[
  {
    "path": "Makefile",
    "content": "clean:\n\tfind . -name \"[._]*.s[a-w][a-z]\" | xargs -i rm -f {} \n.PHONY: clean\n"
  },
  {
    "path": "README.md",
    "content": "## 目录\n\n**注意：** 此项目不再维护，如果想学习本项目可以移步：https://github.com/marmotedu/goserver\n\n**另外**：此课程已经升级为极客时间课程，课程介绍[《Go 语言项目开发实战》](https://time.geekbang.org/column/intro/100079601)，建议学习该课程，更专业、内容更多。\n\n## apiserver_demos 项目介绍\n\n本教程是掘金小册：[基于 Go 语言构建企业级的 RESTful API 服务](https://juejin.cn/book/6844733730678898702) 实战类教学项目,旨在让初学者花尽可能短的时间，通过尽可能详细的步骤，历经 17 个 demo，最终一步步构建出一个生产级的 API 服务器。从开发准备到 API 设计，再到 API 实现、测试和部署，每一步都详细介绍了如何去构建。通过本教程的学习，你将学到如下知识点：\n\n![技术雷达](./docs/技术雷达.png)\n\n知识点很多，跟着教程一节一节进行学习，你将完整的学会如何用 Go 做 API 开发。\n\n## 源码目录介绍\n\n| 目录 | 介绍 |\n| --- | --- | \n| demo01 |实战：启动一个最简单的 RESTful API 服务器 | \n| demo02 |实战：配置文件读取 |\n| demo03 |实战：记录和管理 API 日志 |\n| demo04 |实战：初始化 MySQL 数据库并建立连接 |\n| demo05 |实战：自定义业务错误信息 |\n| demo06 |实战：读取和返回 HTTP 请求 |\n| demo07 |实战：用户业务逻辑处理（业务处理） |\n| demo08 |实战：HTTP 调用添加自定义处理逻辑 |\n| demo09 |实战：API 身份验证 |\n| demo10 |进阶：用 HTTPS 加密 API 请求 |\n| demo11 |进阶：用 Makefile 管理 API 项目 |\n| demo12 |进阶：给 API 命令增加版本功能 |\n| demo13 |进阶：给 API 增加启动脚本 |\n| demo14 |进阶：基于 Nginx 的 API 部署方案 |\n| demo15 |进阶：go test 测试你的代码 |\n| demo16 |进阶：API 性能分析 |\n| demo17 |进阶：生成 Swagger 在线文档 |\n\n## 项目适宜人群\n\n- 掌握一定 Go 语法基础，零 Go 服务器研发经验，想通过一个完整的实战，来系统学习 Go 服务器开发的同学;\n- 有意从事 Go 服务器开发，但尚未入门或入门尚浅的同学;\n- 有过 Go 服务器开发经验，但想了解某一部分构建方法的同学。\n\n\n## 你应该具备什么\n\n- 基本的 Go 语言编程知识\n- 基本的 Linux/Uinx 命令行知识\n"
  },
  {
    "path": "a.go",
    "content": "\n"
  },
  {
    "path": "demo01/README.md",
    "content": "实战：启动一个最简单的RESTful API服务器\n"
  },
  {
    "path": "demo01/handler/sd/check.go",
    "content": "package sd\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/shirou/gopsutil/cpu\"\n\t\"github.com/shirou/gopsutil/disk\"\n\t\"github.com/shirou/gopsutil/load\"\n\t\"github.com/shirou/gopsutil/mem\"\n)\n\nconst (\n\tB  = 1\n\tKB = 1024 * B\n\tMB = 1024 * KB\n\tGB = 1024 * MB\n)\n\n// HealthCheck shows `OK` as the ping-pong result.\nfunc HealthCheck(c *gin.Context) {\n\tmessage := \"OK\"\n\tc.String(http.StatusOK, \"\\n\"+message)\n}\n\n// DiskCheck checks the disk usage.\nfunc DiskCheck(c *gin.Context) {\n\tu, _ := disk.Usage(\"/\")\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusOK\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n\n// CPUCheck checks the cpu usage.\nfunc CPUCheck(c *gin.Context) {\n\tcores, _ := cpu.Counts(false)\n\n\ta, _ := load.Avg()\n\tl1 := a.Load1\n\tl5 := a.Load5\n\tl15 := a.Load15\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif l5 >= float64(cores-1) {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if l5 >= float64(cores-2) {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Load average: %.2f, %.2f, %.2f | Cores: %d\", text, l1, l5, l15, cores)\n\tc.String(status, \"\\n\"+message)\n}\n\n// RAMCheck checks the disk usage.\nfunc RAMCheck(c *gin.Context) {\n\tu, _ := mem.VirtualMemory()\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n"
  },
  {
    "path": "demo01/main.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"log\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"apiserver/router\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc main() {\n\t// Create the Gin engine.\n\tg := gin.New()\n\n\tmiddlewares := []gin.HandlerFunc{}\n\n\t// Routes.\n\trouter.Load(\n\t\t// Cores.\n\t\tg,\n\n\t\t// Middlwares.\n\t\tmiddlewares...,\n\t)\n\n\t// Ping the server to make sure the router is working.\n\tgo func() {\n\t\tif err := pingServer(); err != nil {\n\t\t\tlog.Fatal(\"The router has no response, or it might took too long to start up.\", err)\n\t\t}\n\t\tlog.Print(\"The router has been deployed successfully.\")\n\t}()\n\n\tlog.Printf(\"Start to listening the incoming requests on http address: %s\", \":8080\")\n\tlog.Printf(http.ListenAndServe(\":8080\", g).Error())\n}\n\n// pingServer pings the http server to make sure the router is working.\nfunc pingServer() error {\n\tfor i := 0; i < 2; i++ {\n\t\t// Ping the server by sending a GET request to `/health`.\n\t\tresp, err := http.Get(\"http://127.0.0.1:8080\" + \"/sd/health\")\n\t\tif err == nil && resp.StatusCode == 200 {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Sleep for a second to continue the next ping.\n\t\tlog.Print(\"Waiting for the router, retry in 1 second.\")\n\t\ttime.Sleep(time.Second)\n\t}\n\treturn errors.New(\"Cannot connect to the router.\")\n}\n"
  },
  {
    "path": "demo01/router/middleware/header.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// NoCache is a middleware function that appends headers\n// to prevent the client from caching the HTTP response.\nfunc NoCache(c *gin.Context) {\n\tc.Header(\"Cache-Control\", \"no-cache, no-store, max-age=0, must-revalidate, value\")\n\tc.Header(\"Expires\", \"Thu, 01 Jan 1970 00:00:00 GMT\")\n\tc.Header(\"Last-Modified\", time.Now().UTC().Format(http.TimeFormat))\n\tc.Next()\n}\n\n// Options is a middleware function that appends headers\n// for options requests and aborts then exits the middleware\n// chain and ends the request.\nfunc Options(c *gin.Context) {\n\tif c.Request.Method != \"OPTIONS\" {\n\t\tc.Next()\n\t} else {\n\t\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\t\tc.Header(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Access-Control-Allow-Headers\", \"authorization, origin, content-type, accept\")\n\t\tc.Header(\"Allow\", \"HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Content-Type\", \"application/json\")\n\t\tc.AbortWithStatus(200)\n\t}\n}\n\n// Secure is a middleware function that appends security\n// and resource access headers.\nfunc Secure(c *gin.Context) {\n\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\tc.Header(\"X-Frame-Options\", \"DENY\")\n\tc.Header(\"X-Content-Type-Options\", \"nosniff\")\n\tc.Header(\"X-XSS-Protection\", \"1; mode=block\")\n\tif c.Request.TLS != nil {\n\t\tc.Header(\"Strict-Transport-Security\", \"max-age=31536000\")\n\t}\n\n\t// Also consider adding Content-Security-Policy headers\n\t// c.Header(\"Content-Security-Policy\", \"script-src 'self' https://cdnjs.cloudflare.com\")\n}\n"
  },
  {
    "path": "demo01/router/router.go",
    "content": "package router\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/handler/sd\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Load loads the middlewares, routes, handlers.\nfunc Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {\n\t// Middlewares.\n\tg.Use(gin.Recovery())\n\tg.Use(middleware.NoCache)\n\tg.Use(middleware.Options)\n\tg.Use(middleware.Secure)\n\tg.Use(mw...)\n\t// 404 Handler.\n\tg.NoRoute(func(c *gin.Context) {\n\t\tc.String(http.StatusNotFound, \"The incorrect API route.\")\n\t})\n\n\t// The health check handlers\n\tsvcd := g.Group(\"/sd\")\n\t{\n\t\tsvcd.GET(\"/health\", sd.HealthCheck)\n\t\tsvcd.GET(\"/disk\", sd.DiskCheck)\n\t\tsvcd.GET(\"/cpu\", sd.CPUCheck)\n\t\tsvcd.GET(\"/ram\", sd.RAMCheck)\n\t}\n\n\treturn g\n}\n"
  },
  {
    "path": "demo02/README.md",
    "content": "实战：配置文件读取\n"
  },
  {
    "path": "demo02/conf/config.yaml",
    "content": "runmode: debug                 # 开发模式, debug, release, test\naddr: :8080                  # HTTP绑定端口\nname: apiserver              # API Server的名字\nurl: http://127.0.0.1:8080   # pingServer函数请求的API服务器的ip:port\nmax_ping_count: 10           # pingServer函数try的次数\n"
  },
  {
    "path": "demo02/config/config.go",
    "content": "package config\n\nimport (\n\t\"log\"\n\t\"strings\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/spf13/viper\"\n)\n\ntype Config struct {\n\tName string\n}\n\nfunc Init(cfg string) error {\n\tc := Config{\n\t\tName: cfg,\n\t}\n\n\t// 初始化配置文件\n\tif err := c.initConfig(); err != nil {\n\t\treturn err\n\t}\n\n\t// 监控配置文件变化并热加载程序\n\tc.watchConfig()\n\n\treturn nil\n}\n\nfunc (c *Config) initConfig() error {\n\tif c.Name != \"\" {\n\t\tviper.SetConfigFile(c.Name) // 如果指定了配置文件，则解析指定的配置文件\n\t} else {\n\t\tviper.AddConfigPath(\"conf\") // 如果没有指定配置文件，则解析默认的配置文件\n\t\tviper.SetConfigName(\"config\")\n\t}\n\tviper.SetConfigType(\"yaml\")     // 设置配置文件格式为YAML\n\tviper.AutomaticEnv()            // 读取匹配的环境变量\n\tviper.SetEnvPrefix(\"APISERVER\") // 读取环境变量的前缀为APISERVER\n\treplacer := strings.NewReplacer(\".\", \"_\")\n\tviper.SetEnvKeyReplacer(replacer)\n\tif err := viper.ReadInConfig(); err != nil { // viper解析配置文件\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// 监控配置文件变化并热加载程序\nfunc (c *Config) watchConfig() {\n\tviper.WatchConfig()\n\tviper.OnConfigChange(func(e fsnotify.Event) {\n\t\tlog.Printf(\"Config file changed: %s\", e.Name)\n\t})\n}\n"
  },
  {
    "path": "demo02/handler/sd/check.go",
    "content": "package sd\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/shirou/gopsutil/cpu\"\n\t\"github.com/shirou/gopsutil/disk\"\n\t\"github.com/shirou/gopsutil/load\"\n\t\"github.com/shirou/gopsutil/mem\"\n)\n\nconst (\n\tB  = 1\n\tKB = 1024 * B\n\tMB = 1024 * KB\n\tGB = 1024 * MB\n)\n\n// HealthCheck shows `OK` as the ping-pong result.\nfunc HealthCheck(c *gin.Context) {\n\tmessage := \"OK\"\n\tc.String(http.StatusOK, \"\\n\"+message)\n}\n\n// DiskCheck checks the disk usage.\nfunc DiskCheck(c *gin.Context) {\n\tu, _ := disk.Usage(\"/\")\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusOK\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n\n// CPUCheck checks the cpu usage.\nfunc CPUCheck(c *gin.Context) {\n\tcores, _ := cpu.Counts(false)\n\n\ta, _ := load.Avg()\n\tl1 := a.Load1\n\tl5 := a.Load5\n\tl15 := a.Load15\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif l5 >= float64(cores-1) {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if l5 >= float64(cores-2) {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Load average: %.2f, %.2f, %.2f | Cores: %d\", text, l1, l5, l15, cores)\n\tc.String(status, \"\\n\"+message)\n}\n\n// RAMCheck checks the disk usage.\nfunc RAMCheck(c *gin.Context) {\n\tu, _ := mem.VirtualMemory()\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n"
  },
  {
    "path": "demo02/main.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"log\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"apiserver/config\"\n\t\"apiserver/router\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tcfg = pflag.StringP(\"config\", \"c\", \"\", \"apiserver config file path.\")\n)\n\nfunc main() {\n\tpflag.Parse()\n\n\t// init config\n\tif err := config.Init(*cfg); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Set gin mode.\n\tgin.SetMode(viper.GetString(\"runmode\"))\n\n\t// Create the Gin engine.\n\tg := gin.New()\n\n\tmiddlewares := []gin.HandlerFunc{}\n\n\t// Routes.\n\trouter.Load(\n\t\t// Cores.\n\t\tg,\n\n\t\t// Middlwares.\n\t\tmiddlewares...,\n\t)\n\n\t// Ping the server to make sure the router is working.\n\tgo func() {\n\t\tif err := pingServer(); err != nil {\n\t\t\tlog.Fatal(\"The router has no response, or it might took too long to start up.\", err)\n\t\t}\n\t\tlog.Print(\"The router has been deployed successfully.\")\n\t}()\n\n\tlog.Printf(\"Start to listening the incoming requests on http address: %s\", viper.GetString(\"addr\"))\n\tlog.Printf(http.ListenAndServe(viper.GetString(\"addr\"), g).Error())\n}\n\n// pingServer pings the http server to make sure the router is working.\nfunc pingServer() error {\n\tfor i := 0; i < viper.GetInt(\"max_ping_count\"); i++ {\n\t\t// Ping the server by sending a GET request to `/health`.\n\t\tresp, err := http.Get(viper.GetString(\"url\") + \"/sd/health\")\n\t\tif err == nil && resp.StatusCode == 200 {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Sleep for a second to continue the next ping.\n\t\tlog.Print(\"Waiting for the router, retry in 1 second.\")\n\t\ttime.Sleep(time.Second)\n\t}\n\treturn errors.New(\"Cannot connect to the router.\")\n}\n"
  },
  {
    "path": "demo02/router/middleware/header.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// NoCache is a middleware function that appends headers\n// to prevent the client from caching the HTTP response.\nfunc NoCache(c *gin.Context) {\n\tc.Header(\"Cache-Control\", \"no-cache, no-store, max-age=0, must-revalidate, value\")\n\tc.Header(\"Expires\", \"Thu, 01 Jan 1970 00:00:00 GMT\")\n\tc.Header(\"Last-Modified\", time.Now().UTC().Format(http.TimeFormat))\n\tc.Next()\n}\n\n// Options is a middleware function that appends headers\n// for options requests and aborts then exits the middleware\n// chain and ends the request.\nfunc Options(c *gin.Context) {\n\tif c.Request.Method != \"OPTIONS\" {\n\t\tc.Next()\n\t} else {\n\t\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\t\tc.Header(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Access-Control-Allow-Headers\", \"authorization, origin, content-type, accept\")\n\t\tc.Header(\"Allow\", \"HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Content-Type\", \"application/json\")\n\t\tc.AbortWithStatus(200)\n\t}\n}\n\n// Secure is a middleware function that appends security\n// and resource access headers.\nfunc Secure(c *gin.Context) {\n\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\tc.Header(\"X-Frame-Options\", \"DENY\")\n\tc.Header(\"X-Content-Type-Options\", \"nosniff\")\n\tc.Header(\"X-XSS-Protection\", \"1; mode=block\")\n\tif c.Request.TLS != nil {\n\t\tc.Header(\"Strict-Transport-Security\", \"max-age=31536000\")\n\t}\n\n\t// Also consider adding Content-Security-Policy headers\n\t// c.Header(\"Content-Security-Policy\", \"script-src 'self' https://cdnjs.cloudflare.com\")\n}\n"
  },
  {
    "path": "demo02/router/router.go",
    "content": "package router\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/handler/sd\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Load loads the middlewares, routes, handlers.\nfunc Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {\n\t// Middlewares.\n\tg.Use(gin.Recovery())\n\tg.Use(middleware.NoCache)\n\tg.Use(middleware.Options)\n\tg.Use(middleware.Secure)\n\tg.Use(mw...)\n\t// 404 Handler.\n\tg.NoRoute(func(c *gin.Context) {\n\t\tc.String(http.StatusNotFound, \"The incorrect API route.\")\n\t})\n\n\t// The health check handlers\n\tsvcd := g.Group(\"/sd\")\n\t{\n\t\tsvcd.GET(\"/health\", sd.HealthCheck)\n\t\tsvcd.GET(\"/disk\", sd.DiskCheck)\n\t\tsvcd.GET(\"/cpu\", sd.CPUCheck)\n\t\tsvcd.GET(\"/ram\", sd.RAMCheck)\n\t}\n\n\treturn g\n}\n"
  },
  {
    "path": "demo03/README.md",
    "content": "实战：记录和管理API日志\n"
  },
  {
    "path": "demo03/conf/config.yaml",
    "content": "runmode: debug                 # 开发模式, debug, release, test\naddr: :8080                  # HTTP绑定端口\nname: apiserver              # API Server的名字\nurl: http://127.0.0.1:8080   # pingServer函数请求的API服务器的ip:port\nmax_ping_count: 10           # pingServer函数try的次数\nlog:\n  writers: file,stdout\n  logger_level: DEBUG\n  logger_file: log/apiserver.log\n  log_format_text: false\n  rollingPolicy: size\n  log_rotate_date: 1\n  log_rotate_size: 1\n  log_backup_count: 7\n"
  },
  {
    "path": "demo03/config/config.go",
    "content": "package config\n\nimport (\n\t\"strings\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n)\n\ntype Config struct {\n\tName string\n}\n\nfunc Init(cfg string) error {\n\tc := Config{\n\t\tName: cfg,\n\t}\n\n\t// 初始化配置文件\n\tif err := c.initConfig(); err != nil {\n\t\treturn err\n\t}\n\n\t// 初始化日志包\n\tc.initLog()\n\n\t// 监控配置文件变化并热加载程序\n\tc.watchConfig()\n\n\treturn nil\n}\n\nfunc (c *Config) initConfig() error {\n\tif c.Name != \"\" {\n\t\tviper.SetConfigFile(c.Name) // 如果指定了配置文件，则解析指定的配置文件\n\t} else {\n\t\tviper.AddConfigPath(\"conf\") // 如果没有指定配置文件，则解析默认的配置文件\n\t\tviper.SetConfigName(\"config\")\n\t}\n\tviper.SetConfigType(\"yaml\")     // 设置配置文件格式为YAML\n\tviper.AutomaticEnv()            // 读取匹配的环境变量\n\tviper.SetEnvPrefix(\"APISERVER\") // 读取环境变量的前缀为APISERVER\n\treplacer := strings.NewReplacer(\".\", \"_\")\n\tviper.SetEnvKeyReplacer(replacer)\n\tif err := viper.ReadInConfig(); err != nil { // viper解析配置文件\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *Config) initLog() {\n\tpassLagerCfg := log.PassLagerCfg{\n\t\tWriters:        viper.GetString(\"log.writers\"),\n\t\tLoggerLevel:    viper.GetString(\"log.logger_level\"),\n\t\tLoggerFile:     viper.GetString(\"log.logger_file\"),\n\t\tLogFormatText:  viper.GetBool(\"log.log_format_text\"),\n\t\tRollingPolicy:  viper.GetString(\"log.rollingPolicy\"),\n\t\tLogRotateDate:  viper.GetInt(\"log.log_rotate_date\"),\n\t\tLogRotateSize:  viper.GetInt(\"log.log_rotate_size\"),\n\t\tLogBackupCount: viper.GetInt(\"log.log_backup_count\"),\n\t}\n\n\tlog.InitWithConfig(&passLagerCfg)\n}\n\n// 监控配置文件变化并热加载程序\nfunc (c *Config) watchConfig() {\n\tviper.WatchConfig()\n\tviper.OnConfigChange(func(e fsnotify.Event) {\n\t\tlog.Infof(\"Config file changed: %s\", e.Name)\n\t})\n}\n"
  },
  {
    "path": "demo03/handler/sd/check.go",
    "content": "package sd\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/shirou/gopsutil/cpu\"\n\t\"github.com/shirou/gopsutil/disk\"\n\t\"github.com/shirou/gopsutil/load\"\n\t\"github.com/shirou/gopsutil/mem\"\n)\n\nconst (\n\tB  = 1\n\tKB = 1024 * B\n\tMB = 1024 * KB\n\tGB = 1024 * MB\n)\n\n// HealthCheck shows `OK` as the ping-pong result.\nfunc HealthCheck(c *gin.Context) {\n\tmessage := \"OK\"\n\tc.String(http.StatusOK, \"\\n\"+message)\n}\n\n// DiskCheck checks the disk usage.\nfunc DiskCheck(c *gin.Context) {\n\tu, _ := disk.Usage(\"/\")\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusOK\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n\n// CPUCheck checks the cpu usage.\nfunc CPUCheck(c *gin.Context) {\n\tcores, _ := cpu.Counts(false)\n\n\ta, _ := load.Avg()\n\tl1 := a.Load1\n\tl5 := a.Load5\n\tl15 := a.Load15\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif l5 >= float64(cores-1) {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if l5 >= float64(cores-2) {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Load average: %.2f, %.2f, %.2f | Cores: %d\", text, l1, l5, l15, cores)\n\tc.String(status, \"\\n\"+message)\n}\n\n// RAMCheck checks the disk usage.\nfunc RAMCheck(c *gin.Context) {\n\tu, _ := mem.VirtualMemory()\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n"
  },
  {
    "path": "demo03/main.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"apiserver/config\"\n\t\"apiserver/router\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tcfg = pflag.StringP(\"config\", \"c\", \"\", \"apiserver config file path.\")\n)\n\nfunc main() {\n\tpflag.Parse()\n\n\t// init config\n\tif err := config.Init(*cfg); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Set gin mode.\n\tgin.SetMode(viper.GetString(\"runmode\"))\n\n\t// Create the Gin engine.\n\tg := gin.New()\n\n\tmiddlewares := []gin.HandlerFunc{}\n\n\t// Routes.\n\trouter.Load(\n\t\t// Cores.\n\t\tg,\n\n\t\t// Middlwares.\n\t\tmiddlewares...,\n\t)\n\n\t// Ping the server to make sure the router is working.\n\tgo func() {\n\t\tif err := pingServer(); err != nil {\n\t\t\tlog.Fatal(\"The router has no response, or it might took too long to start up.\", err)\n\t\t}\n\t\tlog.Info(\"The router has been deployed successfully.\")\n\t}()\n\n\tlog.Infof(\"Start to listening the incoming requests on http address: %s\", viper.GetString(\"addr\"))\n\tlog.Info(http.ListenAndServe(viper.GetString(\"addr\"), g).Error())\n}\n\n// pingServer pings the http server to make sure the router is working.\nfunc pingServer() error {\n\tfor i := 0; i < viper.GetInt(\"max_ping_count\"); i++ {\n\t\t// Ping the server by sending a GET request to `/health`.\n\t\tresp, err := http.Get(viper.GetString(\"url\") + \"/sd/health\")\n\t\tif err == nil && resp.StatusCode == 200 {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Sleep for a second to continue the next ping.\n\t\tlog.Info(\"Waiting for the router, retry in 1 second.\")\n\t\ttime.Sleep(time.Second)\n\t}\n\treturn errors.New(\"Cannot connect to the router.\")\n}\n"
  },
  {
    "path": "demo03/router/middleware/header.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// NoCache is a middleware function that appends headers\n// to prevent the client from caching the HTTP response.\nfunc NoCache(c *gin.Context) {\n\tc.Header(\"Cache-Control\", \"no-cache, no-store, max-age=0, must-revalidate, value\")\n\tc.Header(\"Expires\", \"Thu, 01 Jan 1970 00:00:00 GMT\")\n\tc.Header(\"Last-Modified\", time.Now().UTC().Format(http.TimeFormat))\n\tc.Next()\n}\n\n// Options is a middleware function that appends headers\n// for options requests and aborts then exits the middleware\n// chain and ends the request.\nfunc Options(c *gin.Context) {\n\tif c.Request.Method != \"OPTIONS\" {\n\t\tc.Next()\n\t} else {\n\t\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\t\tc.Header(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Access-Control-Allow-Headers\", \"authorization, origin, content-type, accept\")\n\t\tc.Header(\"Allow\", \"HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Content-Type\", \"application/json\")\n\t\tc.AbortWithStatus(200)\n\t}\n}\n\n// Secure is a middleware function that appends security\n// and resource access headers.\nfunc Secure(c *gin.Context) {\n\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\tc.Header(\"X-Frame-Options\", \"DENY\")\n\tc.Header(\"X-Content-Type-Options\", \"nosniff\")\n\tc.Header(\"X-XSS-Protection\", \"1; mode=block\")\n\tif c.Request.TLS != nil {\n\t\tc.Header(\"Strict-Transport-Security\", \"max-age=31536000\")\n\t}\n\n\t// Also consider adding Content-Security-Policy headers\n\t// c.Header(\"Content-Security-Policy\", \"script-src 'self' https://cdnjs.cloudflare.com\")\n}\n"
  },
  {
    "path": "demo03/router/router.go",
    "content": "package router\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/handler/sd\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Load loads the middlewares, routes, handlers.\nfunc Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {\n\t// Middlewares.\n\tg.Use(gin.Recovery())\n\tg.Use(middleware.NoCache)\n\tg.Use(middleware.Options)\n\tg.Use(middleware.Secure)\n\tg.Use(mw...)\n\t// 404 Handler.\n\tg.NoRoute(func(c *gin.Context) {\n\t\tc.String(http.StatusNotFound, \"The incorrect API route.\")\n\t})\n\n\t// The health check handlers\n\tsvcd := g.Group(\"/sd\")\n\t{\n\t\tsvcd.GET(\"/health\", sd.HealthCheck)\n\t\tsvcd.GET(\"/disk\", sd.DiskCheck)\n\t\tsvcd.GET(\"/cpu\", sd.CPUCheck)\n\t\tsvcd.GET(\"/ram\", sd.RAMCheck)\n\t}\n\n\treturn g\n}\n"
  },
  {
    "path": "demo04/README.md",
    "content": "实战：初始化Mysql数据库并建立连接\n"
  },
  {
    "path": "demo04/conf/config.yaml",
    "content": "runmode: debug                 # 开发模式, debug, release, test\naddr: :8080                  # HTTP绑定端口\nname: apiserver              # API Server的名字\nurl: http://127.0.0.1:8080   # pingServer函数请求的API服务器的ip:port\nmax_ping_count: 10           # pingServer函数try的次数\nlog:\n  writers: file,stdout\n  logger_level: DEBUG\n  logger_file: log/apiserver.log\n  log_format_text: false\n  rollingPolicy: size\n  log_rotate_date: 1\n  log_rotate_size: 1\n  log_backup_count: 7\n"
  },
  {
    "path": "demo04/config/config.go",
    "content": "package config\n\nimport (\n\t\"strings\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n)\n\ntype Config struct {\n\tName string\n}\n\nfunc Init(cfg string) error {\n\tc := Config{\n\t\tName: cfg,\n\t}\n\n\t// 初始化配置文件\n\tif err := c.initConfig(); err != nil {\n\t\treturn err\n\t}\n\n\t// 初始化日志包\n\tc.initLog()\n\n\t// 监控配置文件变化并热加载程序\n\tc.watchConfig()\n\n\treturn nil\n}\n\nfunc (c *Config) initConfig() error {\n\tif c.Name != \"\" {\n\t\tviper.SetConfigFile(c.Name) // 如果指定了配置文件，则解析指定的配置文件\n\t} else {\n\t\tviper.AddConfigPath(\"conf\") // 如果没有指定配置文件，则解析默认的配置文件\n\t\tviper.SetConfigName(\"config\")\n\t}\n\tviper.SetConfigType(\"yaml\")     // 设置配置文件格式为YAML\n\tviper.AutomaticEnv()            // 读取匹配的环境变量\n\tviper.SetEnvPrefix(\"APISERVER\") // 读取环境变量的前缀为APISERVER\n\treplacer := strings.NewReplacer(\".\", \"_\")\n\tviper.SetEnvKeyReplacer(replacer)\n\tif err := viper.ReadInConfig(); err != nil { // viper解析配置文件\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *Config) initLog() {\n\tpassLagerCfg := log.PassLagerCfg{\n\t\tWriters:        viper.GetString(\"log.writers\"),\n\t\tLoggerLevel:    viper.GetString(\"log.logger_level\"),\n\t\tLoggerFile:     viper.GetString(\"log.logger_file\"),\n\t\tLogFormatText:  viper.GetBool(\"log.log_format_text\"),\n\t\tRollingPolicy:  viper.GetString(\"log.rollingPolicy\"),\n\t\tLogRotateDate:  viper.GetInt(\"log.log_rotate_date\"),\n\t\tLogRotateSize:  viper.GetInt(\"log.log_rotate_size\"),\n\t\tLogBackupCount: viper.GetInt(\"log.log_backup_count\"),\n\t}\n\n\tlog.InitWithConfig(&passLagerCfg)\n}\n\n// 监控配置文件变化并热加载程序\nfunc (c *Config) watchConfig() {\n\tviper.WatchConfig()\n\tviper.OnConfigChange(func(e fsnotify.Event) {\n\t\tlog.Infof(\"Config file changed: %s\", e.Name)\n\t})\n}\n"
  },
  {
    "path": "demo04/handler/sd/check.go",
    "content": "package sd\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/shirou/gopsutil/cpu\"\n\t\"github.com/shirou/gopsutil/disk\"\n\t\"github.com/shirou/gopsutil/load\"\n\t\"github.com/shirou/gopsutil/mem\"\n)\n\nconst (\n\tB  = 1\n\tKB = 1024 * B\n\tMB = 1024 * KB\n\tGB = 1024 * MB\n)\n\n// HealthCheck shows `OK` as the ping-pong result.\nfunc HealthCheck(c *gin.Context) {\n\tmessage := \"OK\"\n\tc.String(http.StatusOK, \"\\n\"+message)\n}\n\n// DiskCheck checks the disk usage.\nfunc DiskCheck(c *gin.Context) {\n\tu, _ := disk.Usage(\"/\")\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusOK\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n\n// CPUCheck checks the cpu usage.\nfunc CPUCheck(c *gin.Context) {\n\tcores, _ := cpu.Counts(false)\n\n\ta, _ := load.Avg()\n\tl1 := a.Load1\n\tl5 := a.Load5\n\tl15 := a.Load15\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif l5 >= float64(cores-1) {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if l5 >= float64(cores-2) {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Load average: %.2f, %.2f, %.2f | Cores: %d\", text, l1, l5, l15, cores)\n\tc.String(status, \"\\n\"+message)\n}\n\n// RAMCheck checks the disk usage.\nfunc RAMCheck(c *gin.Context) {\n\tu, _ := mem.VirtualMemory()\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n"
  },
  {
    "path": "demo04/main.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"apiserver/config\"\n\t\"apiserver/model\"\n\t\"apiserver/router\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tcfg = pflag.StringP(\"config\", \"c\", \"\", \"apiserver config file path.\")\n)\n\nfunc main() {\n\tpflag.Parse()\n\n\t// init config\n\tif err := config.Init(*cfg); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// init db\n\tmodel.DB.Init()\n\tdefer model.DB.Close()\n\n\t// Set gin mode.\n\tgin.SetMode(viper.GetString(\"runmode\"))\n\n\t// Create the Gin engine.\n\tg := gin.New()\n\n\tmiddlewares := []gin.HandlerFunc{}\n\n\t// Routes.\n\trouter.Load(\n\t\t// Cores.\n\t\tg,\n\n\t\t// Middlwares.\n\t\tmiddlewares...,\n\t)\n\n\t// Ping the server to make sure the router is working.\n\tgo func() {\n\t\tif err := pingServer(); err != nil {\n\t\t\tlog.Fatal(\"The router has no response, or it might took too long to start up.\", err)\n\t\t}\n\t\tlog.Info(\"The router has been deployed successfully.\")\n\t}()\n\n\tlog.Infof(\"Start to listening the incoming requests on http address: %s\", viper.GetString(\"addr\"))\n\tlog.Info(http.ListenAndServe(viper.GetString(\"addr\"), g).Error())\n}\n\n// pingServer pings the http server to make sure the router is working.\nfunc pingServer() error {\n\tfor i := 0; i < viper.GetInt(\"max_ping_count\"); i++ {\n\t\t// Ping the server by sending a GET request to `/health`.\n\t\tresp, err := http.Get(viper.GetString(\"url\") + \"/sd/health\")\n\t\tif err == nil && resp.StatusCode == 200 {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Sleep for a second to continue the next ping.\n\t\tlog.Info(\"Waiting for the router, retry in 1 second.\")\n\t\ttime.Sleep(time.Second)\n\t}\n\treturn errors.New(\"Cannot connect to the router.\")\n}\n"
  },
  {
    "path": "demo04/model/init.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n\t// MySQL driver.\n\t\"github.com/jinzhu/gorm\"\n\t_ \"github.com/jinzhu/gorm/dialects/mysql\"\n)\n\ntype Database struct {\n\tSelf   *gorm.DB\n\tDocker *gorm.DB\n}\n\nvar DB *Database\n\nfunc openDB(username, password, addr, name string) *gorm.DB {\n\tconfig := fmt.Sprintf(\"%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s\",\n\t\tusername,\n\t\tpassword,\n\t\taddr,\n\t\tname,\n\t\ttrue,\n\t\t//\"Asia/Shanghai\"),\n\t\t\"Local\")\n\n\tdb, err := gorm.Open(\"mysql\", config)\n\tif err != nil {\n\t\tlog.Errorf(err, \"Database connection failed. Database name: %s\", name)\n\t}\n\n\t// set for db connection\n\tsetupDB(db)\n\n\treturn db\n}\n\nfunc setupDB(db *gorm.DB) {\n\tdb.LogMode(viper.GetBool(\"gormlog\"))\n\t//db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数，默认值为0表示不限制.设置最大的连接数，可以避免并发太高导致连接mysql出现too many connections的错误。\n\tdb.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。\n}\n\n// used for cli\nfunc InitSelfDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"db.username\"),\n\t\tviper.GetString(\"db.password\"),\n\t\tviper.GetString(\"db.addr\"),\n\t\tviper.GetString(\"db.name\"))\n}\n\nfunc GetSelfDB() *gorm.DB {\n\treturn InitSelfDB()\n}\n\nfunc InitDockerDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"docker_db.username\"),\n\t\tviper.GetString(\"docker_db.password\"),\n\t\tviper.GetString(\"docker_db.addr\"),\n\t\tviper.GetString(\"docker_db.name\"))\n}\n\nfunc GetDockerDB() *gorm.DB {\n\treturn InitDockerDB()\n}\n\nfunc (db *Database) Init() {\n\tDB = &Database{\n\t\tSelf:   GetSelfDB(),\n\t\tDocker: GetDockerDB(),\n\t}\n}\n\nfunc (db *Database) Close() {\n\tDB.Self.Close()\n\tDB.Docker.Close()\n}\n"
  },
  {
    "path": "demo04/router/middleware/header.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// NoCache is a middleware function that appends headers\n// to prevent the client from caching the HTTP response.\nfunc NoCache(c *gin.Context) {\n\tc.Header(\"Cache-Control\", \"no-cache, no-store, max-age=0, must-revalidate, value\")\n\tc.Header(\"Expires\", \"Thu, 01 Jan 1970 00:00:00 GMT\")\n\tc.Header(\"Last-Modified\", time.Now().UTC().Format(http.TimeFormat))\n\tc.Next()\n}\n\n// Options is a middleware function that appends headers\n// for options requests and aborts then exits the middleware\n// chain and ends the request.\nfunc Options(c *gin.Context) {\n\tif c.Request.Method != \"OPTIONS\" {\n\t\tc.Next()\n\t} else {\n\t\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\t\tc.Header(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Access-Control-Allow-Headers\", \"authorization, origin, content-type, accept\")\n\t\tc.Header(\"Allow\", \"HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Content-Type\", \"application/json\")\n\t\tc.AbortWithStatus(200)\n\t}\n}\n\n// Secure is a middleware function that appends security\n// and resource access headers.\nfunc Secure(c *gin.Context) {\n\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\tc.Header(\"X-Frame-Options\", \"DENY\")\n\tc.Header(\"X-Content-Type-Options\", \"nosniff\")\n\tc.Header(\"X-XSS-Protection\", \"1; mode=block\")\n\tif c.Request.TLS != nil {\n\t\tc.Header(\"Strict-Transport-Security\", \"max-age=31536000\")\n\t}\n\n\t// Also consider adding Content-Security-Policy headers\n\t// c.Header(\"Content-Security-Policy\", \"script-src 'self' https://cdnjs.cloudflare.com\")\n}\n"
  },
  {
    "path": "demo04/router/router.go",
    "content": "package router\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/handler/sd\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Load loads the middlewares, routes, handlers.\nfunc Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {\n\t// Middlewares.\n\tg.Use(gin.Recovery())\n\tg.Use(middleware.NoCache)\n\tg.Use(middleware.Options)\n\tg.Use(middleware.Secure)\n\tg.Use(mw...)\n\t// 404 Handler.\n\tg.NoRoute(func(c *gin.Context) {\n\t\tc.String(http.StatusNotFound, \"The incorrect API route.\")\n\t})\n\n\t// The health check handlers\n\tsvcd := g.Group(\"/sd\")\n\t{\n\t\tsvcd.GET(\"/health\", sd.HealthCheck)\n\t\tsvcd.GET(\"/disk\", sd.DiskCheck)\n\t\tsvcd.GET(\"/cpu\", sd.CPUCheck)\n\t\tsvcd.GET(\"/ram\", sd.RAMCheck)\n\t}\n\n\treturn g\n}\n"
  },
  {
    "path": "demo05/README.md",
    "content": "实战：自定义业务错误信息\n"
  },
  {
    "path": "demo05/conf/config.yaml",
    "content": "runmode: debug                 # 开发模式, debug, release, test\naddr: :8080                  # HTTP绑定端口\nname: apiserver              # API Server的名字\nurl: http://127.0.0.1:8080   # pingServer函数请求的API服务器的ip:port\nmax_ping_count: 10           # pingServer函数try的次数\nlog:\n  writers: file,stdout\n  logger_level: DEBUG\n  logger_file: log/apiserver.log\n  log_format_text: false\n  rollingPolicy: size\n  log_rotate_date: 1\n  log_rotate_size: 1\n  log_backup_count: 7\n"
  },
  {
    "path": "demo05/config/config.go",
    "content": "package config\n\nimport (\n\t\"strings\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n)\n\ntype Config struct {\n\tName string\n}\n\nfunc Init(cfg string) error {\n\tc := Config{\n\t\tName: cfg,\n\t}\n\n\t// 初始化配置文件\n\tif err := c.initConfig(); err != nil {\n\t\treturn err\n\t}\n\n\t// 初始化日志包\n\tc.initLog()\n\n\t// 监控配置文件变化并热加载程序\n\tc.watchConfig()\n\n\treturn nil\n}\n\nfunc (c *Config) initConfig() error {\n\tif c.Name != \"\" {\n\t\tviper.SetConfigFile(c.Name) // 如果指定了配置文件，则解析指定的配置文件\n\t} else {\n\t\tviper.AddConfigPath(\"conf\") // 如果没有指定配置文件，则解析默认的配置文件\n\t\tviper.SetConfigName(\"config\")\n\t}\n\tviper.SetConfigType(\"yaml\")     // 设置配置文件格式为YAML\n\tviper.AutomaticEnv()            // 读取匹配的环境变量\n\tviper.SetEnvPrefix(\"APISERVER\") // 读取环境变量的前缀为APISERVER\n\treplacer := strings.NewReplacer(\".\", \"_\")\n\tviper.SetEnvKeyReplacer(replacer)\n\tif err := viper.ReadInConfig(); err != nil { // viper解析配置文件\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *Config) initLog() {\n\tpassLagerCfg := log.PassLagerCfg{\n\t\tWriters:        viper.GetString(\"log.writers\"),\n\t\tLoggerLevel:    viper.GetString(\"log.logger_level\"),\n\t\tLoggerFile:     viper.GetString(\"log.logger_file\"),\n\t\tLogFormatText:  viper.GetBool(\"log.log_format_text\"),\n\t\tRollingPolicy:  viper.GetString(\"log.rollingPolicy\"),\n\t\tLogRotateDate:  viper.GetInt(\"log.log_rotate_date\"),\n\t\tLogRotateSize:  viper.GetInt(\"log.log_rotate_size\"),\n\t\tLogBackupCount: viper.GetInt(\"log.log_backup_count\"),\n\t}\n\n\tlog.InitWithConfig(&passLagerCfg)\n}\n\n// 监控配置文件变化并热加载程序\nfunc (c *Config) watchConfig() {\n\tviper.WatchConfig()\n\tviper.OnConfigChange(func(e fsnotify.Event) {\n\t\tlog.Infof(\"Config file changed: %s\", e.Name)\n\t})\n}\n"
  },
  {
    "path": "demo05/handler/sd/check.go",
    "content": "package sd\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/shirou/gopsutil/cpu\"\n\t\"github.com/shirou/gopsutil/disk\"\n\t\"github.com/shirou/gopsutil/load\"\n\t\"github.com/shirou/gopsutil/mem\"\n)\n\nconst (\n\tB  = 1\n\tKB = 1024 * B\n\tMB = 1024 * KB\n\tGB = 1024 * MB\n)\n\n// HealthCheck shows `OK` as the ping-pong result.\nfunc HealthCheck(c *gin.Context) {\n\tmessage := \"OK\"\n\tc.String(http.StatusOK, \"\\n\"+message)\n}\n\n// DiskCheck checks the disk usage.\nfunc DiskCheck(c *gin.Context) {\n\tu, _ := disk.Usage(\"/\")\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusOK\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n\n// CPUCheck checks the cpu usage.\nfunc CPUCheck(c *gin.Context) {\n\tcores, _ := cpu.Counts(false)\n\n\ta, _ := load.Avg()\n\tl1 := a.Load1\n\tl5 := a.Load5\n\tl15 := a.Load15\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif l5 >= float64(cores-1) {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if l5 >= float64(cores-2) {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Load average: %.2f, %.2f, %.2f | Cores: %d\", text, l1, l5, l15, cores)\n\tc.String(status, \"\\n\"+message)\n}\n\n// RAMCheck checks the disk usage.\nfunc RAMCheck(c *gin.Context) {\n\tu, _ := mem.VirtualMemory()\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n"
  },
  {
    "path": "demo05/handler/user/create.go",
    "content": "package user\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n)\n\n// Create creates a new user account.\nfunc Create(c *gin.Context) {\n\tvar r struct {\n\t\tUsername string `json:\"username\"`\n\t\tPassword string `json:\"password\"`\n\t}\n\n\tvar err error\n\tif err := c.Bind(&r); err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\"error\": errno.ErrBind})\n\t\treturn\n\t}\n\n\tlog.Debugf(\"username is: [%s], password is [%s]\", r.Username, r.Password)\n\tif r.Username == \"\" {\n\t\terr = errno.New(errno.ErrUserNotFound, fmt.Errorf(\"username can not found in db: xx.xx.xx.xx\")).Add(\"This is add message.\")\n\t\tlog.Errorf(err, \"Get an error\")\n\t}\n\n\tif errno.IsErrUserNotFound(err) {\n\t\tlog.Debug(\"err type is ErrUserNotFound\")\n\t}\n\n\tif r.Password == \"\" {\n\t\terr = fmt.Errorf(\"password is empty\")\n\t}\n\n\tcode, message := errno.DecodeErr(err)\n\tc.JSON(http.StatusOK, gin.H{\"code\": code, \"message\": message})\n}\n"
  },
  {
    "path": "demo05/main.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"apiserver/config\"\n\t\"apiserver/model\"\n\t\"apiserver/router\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tcfg = pflag.StringP(\"config\", \"c\", \"\", \"apiserver config file path.\")\n)\n\nfunc main() {\n\tpflag.Parse()\n\n\t// init config\n\tif err := config.Init(*cfg); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// init db\n\tmodel.DB.Init()\n\tdefer model.DB.Close()\n\n\t// Set gin mode.\n\tgin.SetMode(viper.GetString(\"runmode\"))\n\n\t// Create the Gin engine.\n\tg := gin.New()\n\n\tmiddlewares := []gin.HandlerFunc{}\n\n\t// Routes.\n\trouter.Load(\n\t\t// Cores.\n\t\tg,\n\n\t\t// Middlwares.\n\t\tmiddlewares...,\n\t)\n\n\t// Ping the server to make sure the router is working.\n\tgo func() {\n\t\tif err := pingServer(); err != nil {\n\t\t\tlog.Fatal(\"The router has no response, or it might took too long to start up.\", err)\n\t\t}\n\t\tlog.Info(\"The router has been deployed successfully.\")\n\t}()\n\n\tlog.Infof(\"Start to listening the incoming requests on http address: %s\", viper.GetString(\"addr\"))\n\tlog.Info(http.ListenAndServe(viper.GetString(\"addr\"), g).Error())\n}\n\n// pingServer pings the http server to make sure the router is working.\nfunc pingServer() error {\n\tfor i := 0; i < viper.GetInt(\"max_ping_count\"); i++ {\n\t\t// Ping the server by sending a GET request to `/health`.\n\t\tresp, err := http.Get(viper.GetString(\"url\") + \"/sd/health\")\n\t\tif err == nil && resp.StatusCode == 200 {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Sleep for a second to continue the next ping.\n\t\tlog.Info(\"Waiting for the router, retry in 1 second.\")\n\t\ttime.Sleep(time.Second)\n\t}\n\treturn errors.New(\"Cannot connect to the router.\")\n}\n"
  },
  {
    "path": "demo05/model/init.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n\t// MySQL driver.\n\t\"github.com/jinzhu/gorm\"\n\t_ \"github.com/jinzhu/gorm/dialects/mysql\"\n)\n\ntype Database struct {\n\tSelf   *gorm.DB\n\tDocker *gorm.DB\n}\n\nvar DB *Database\n\nfunc openDB(username, password, addr, name string) *gorm.DB {\n\tconfig := fmt.Sprintf(\"%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s\",\n\t\tusername,\n\t\tpassword,\n\t\taddr,\n\t\tname,\n\t\ttrue,\n\t\t//\"Asia/Shanghai\"),\n\t\t\"Local\")\n\n\tdb, err := gorm.Open(\"mysql\", config)\n\tif err != nil {\n\t\tlog.Errorf(err, \"Database connection failed. Database name: %s\", name)\n\t}\n\n\t// set for db connection\n\tsetupDB(db)\n\n\treturn db\n}\n\nfunc setupDB(db *gorm.DB) {\n\tdb.LogMode(viper.GetBool(\"gormlog\"))\n\t//db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数，默认值为0表示不限制.设置最大的连接数，可以避免并发太高导致连接mysql出现too many connections的错误。\n\tdb.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。\n}\n\n// used for cli\nfunc InitSelfDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"db.username\"),\n\t\tviper.GetString(\"db.password\"),\n\t\tviper.GetString(\"db.addr\"),\n\t\tviper.GetString(\"db.name\"))\n}\n\nfunc GetSelfDB() *gorm.DB {\n\treturn InitSelfDB()\n}\n\nfunc InitDockerDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"docker_db.username\"),\n\t\tviper.GetString(\"docker_db.password\"),\n\t\tviper.GetString(\"docker_db.addr\"),\n\t\tviper.GetString(\"docker_db.name\"))\n}\n\nfunc GetDockerDB() *gorm.DB {\n\treturn InitDockerDB()\n}\n\nfunc (db *Database) Init() {\n\tDB = &Database{\n\t\tSelf:   GetSelfDB(),\n\t\tDocker: GetDockerDB(),\n\t}\n}\n\nfunc (db *Database) Close() {\n\tDB.Self.Close()\n\tDB.Docker.Close()\n}\n"
  },
  {
    "path": "demo05/pkg/errno/code.go",
    "content": "package errno\n\nvar (\n\t// Common errors\n\tOK                  = &Errno{Code: 0, Message: \"OK\"}\n\tInternalServerError = &Errno{Code: 10001, Message: \"Internal server error.\"}\n\tErrBind             = &Errno{Code: 10002, Message: \"Error occurred while binding the request body to the struct.\"}\n\n\t// user errors\n\tErrUserNotFound = &Errno{Code: 20102, Message: \"The user was not found.\"}\n)\n"
  },
  {
    "path": "demo05/pkg/errno/errno.go",
    "content": "package errno\n\nimport \"fmt\"\n\ntype Errno struct {\n\tCode    int\n\tMessage string\n}\n\nfunc (err Errno) Error() string {\n\treturn err.Message\n}\n\n// Err represents an error\ntype Err struct {\n\tCode    int\n\tMessage string\n\tErr     error\n}\n\nfunc New(errno *Errno, err error) *Err {\n\treturn &Err{Code: errno.Code, Message: errno.Message, Err: err}\n}\n\nfunc (err *Err) Add(message string) error {\n\t//err.Message = fmt.Sprintf(\"%s %s\", err.Message, message)\n\terr.Message += \" \" + message\n\treturn err\n}\n\nfunc (err *Err) Addf(format string, args ...interface{}) error {\n\t//return err.Message = fmt.Sprintf(\"%s %s\", err.Message, fmt.Sprintf(format, args...))\n\terr.Message += \" \" + fmt.Sprintf(format, args...)\n\treturn err\n}\n\nfunc (err *Err) Error() string {\n\treturn fmt.Sprintf(\"Err - code: %d, message: %s, error: %s\", err.Code, err.Message, err.Err)\n}\n\nfunc IsErrUserNotFound(err error) bool {\n\tcode, _ := DecodeErr(err)\n\treturn code == ErrUserNotFound.Code\n}\n\nfunc DecodeErr(err error) (int, string) {\n\tif err == nil {\n\t\treturn OK.Code, OK.Message\n\t}\n\n\tswitch typed := err.(type) {\n\tcase *Err:\n\t\treturn typed.Code, typed.Message\n\tcase *Errno:\n\t\treturn typed.Code, typed.Message\n\tdefault:\n\t}\n\n\treturn InternalServerError.Code, err.Error()\n}\n"
  },
  {
    "path": "demo05/router/middleware/header.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// NoCache is a middleware function that appends headers\n// to prevent the client from caching the HTTP response.\nfunc NoCache(c *gin.Context) {\n\tc.Header(\"Cache-Control\", \"no-cache, no-store, max-age=0, must-revalidate, value\")\n\tc.Header(\"Expires\", \"Thu, 01 Jan 1970 00:00:00 GMT\")\n\tc.Header(\"Last-Modified\", time.Now().UTC().Format(http.TimeFormat))\n\tc.Next()\n}\n\n// Options is a middleware function that appends headers\n// for options requests and aborts then exits the middleware\n// chain and ends the request.\nfunc Options(c *gin.Context) {\n\tif c.Request.Method != \"OPTIONS\" {\n\t\tc.Next()\n\t} else {\n\t\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\t\tc.Header(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Access-Control-Allow-Headers\", \"authorization, origin, content-type, accept\")\n\t\tc.Header(\"Allow\", \"HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Content-Type\", \"application/json\")\n\t\tc.AbortWithStatus(200)\n\t}\n}\n\n// Secure is a middleware function that appends security\n// and resource access headers.\nfunc Secure(c *gin.Context) {\n\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\tc.Header(\"X-Frame-Options\", \"DENY\")\n\tc.Header(\"X-Content-Type-Options\", \"nosniff\")\n\tc.Header(\"X-XSS-Protection\", \"1; mode=block\")\n\tif c.Request.TLS != nil {\n\t\tc.Header(\"Strict-Transport-Security\", \"max-age=31536000\")\n\t}\n\n\t// Also consider adding Content-Security-Policy headers\n\t// c.Header(\"Content-Security-Policy\", \"script-src 'self' https://cdnjs.cloudflare.com\")\n}\n"
  },
  {
    "path": "demo05/router/router.go",
    "content": "package router\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/handler/sd\"\n\t\"apiserver/handler/user\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Load loads the middlewares, routes, handlers.\nfunc Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {\n\t// Middlewares.\n\tg.Use(gin.Recovery())\n\tg.Use(middleware.NoCache)\n\tg.Use(middleware.Options)\n\tg.Use(middleware.Secure)\n\tg.Use(mw...)\n\t// 404 Handler.\n\tg.NoRoute(func(c *gin.Context) {\n\t\tc.String(http.StatusNotFound, \"The incorrect API route.\")\n\t})\n\n\tu := g.Group(\"/v1/user\")\n\t{\n\t\tu.POST(\"\", user.Create)\n\t}\n\n\t// The health check handlers\n\tsvcd := g.Group(\"/sd\")\n\t{\n\t\tsvcd.GET(\"/health\", sd.HealthCheck)\n\t\tsvcd.GET(\"/disk\", sd.DiskCheck)\n\t\tsvcd.GET(\"/cpu\", sd.CPUCheck)\n\t\tsvcd.GET(\"/ram\", sd.RAMCheck)\n\t}\n\n\treturn g\n}\n"
  },
  {
    "path": "demo06/README.md",
    "content": "实战：读取和返回HTTP请求\n"
  },
  {
    "path": "demo06/conf/config.yaml",
    "content": "runmode: debug                 # 开发模式, debug, release, test\naddr: :8080                  # HTTP绑定端口\nname: apiserver              # API Server的名字\nurl: http://127.0.0.1:8080   # pingServer函数请求的API服务器的ip:port\nmax_ping_count: 10           # pingServer函数try的次数\nlog:\n  writers: file,stdout\n  logger_level: DEBUG\n  logger_file: log/apiserver.log\n  log_format_text: false\n  rollingPolicy: size\n  log_rotate_date: 1\n  log_rotate_size: 1\n  log_backup_count: 7\n"
  },
  {
    "path": "demo06/config/config.go",
    "content": "package config\n\nimport (\n\t\"strings\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n)\n\ntype Config struct {\n\tName string\n}\n\nfunc Init(cfg string) error {\n\tc := Config{\n\t\tName: cfg,\n\t}\n\n\t// 初始化配置文件\n\tif err := c.initConfig(); err != nil {\n\t\treturn err\n\t}\n\n\t// 初始化日志包\n\tc.initLog()\n\n\t// 监控配置文件变化并热加载程序\n\tc.watchConfig()\n\n\treturn nil\n}\n\nfunc (c *Config) initConfig() error {\n\tif c.Name != \"\" {\n\t\tviper.SetConfigFile(c.Name) // 如果指定了配置文件，则解析指定的配置文件\n\t} else {\n\t\tviper.AddConfigPath(\"conf\") // 如果没有指定配置文件，则解析默认的配置文件\n\t\tviper.SetConfigName(\"config\")\n\t}\n\tviper.SetConfigType(\"yaml\")     // 设置配置文件格式为YAML\n\tviper.AutomaticEnv()            // 读取匹配的环境变量\n\tviper.SetEnvPrefix(\"APISERVER\") // 读取环境变量的前缀为APISERVER\n\treplacer := strings.NewReplacer(\".\", \"_\")\n\tviper.SetEnvKeyReplacer(replacer)\n\tif err := viper.ReadInConfig(); err != nil { // viper解析配置文件\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *Config) initLog() {\n\tpassLagerCfg := log.PassLagerCfg{\n\t\tWriters:        viper.GetString(\"log.writers\"),\n\t\tLoggerLevel:    viper.GetString(\"log.logger_level\"),\n\t\tLoggerFile:     viper.GetString(\"log.logger_file\"),\n\t\tLogFormatText:  viper.GetBool(\"log.log_format_text\"),\n\t\tRollingPolicy:  viper.GetString(\"log.rollingPolicy\"),\n\t\tLogRotateDate:  viper.GetInt(\"log.log_rotate_date\"),\n\t\tLogRotateSize:  viper.GetInt(\"log.log_rotate_size\"),\n\t\tLogBackupCount: viper.GetInt(\"log.log_backup_count\"),\n\t}\n\n\tlog.InitWithConfig(&passLagerCfg)\n}\n\n// 监控配置文件变化并热加载程序\nfunc (c *Config) watchConfig() {\n\tviper.WatchConfig()\n\tviper.OnConfigChange(func(e fsnotify.Event) {\n\t\tlog.Infof(\"Config file changed: %s\", e.Name)\n\t})\n}\n"
  },
  {
    "path": "demo06/handler/handler.go",
    "content": "package handler\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype Response struct {\n\tCode    int         `json:\"code\"`\n\tMessage string      `json:\"message\"`\n\tData    interface{} `json:\"data\"`\n}\n\nfunc SendResponse(c *gin.Context, err error, data interface{}) {\n\tcode, message := errno.DecodeErr(err)\n\n\t// always return http.StatusOK\n\tc.JSON(http.StatusOK, Response{\n\t\tCode:    code,\n\t\tMessage: message,\n\t\tData:    data,\n\t})\n}\n"
  },
  {
    "path": "demo06/handler/sd/check.go",
    "content": "package sd\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/shirou/gopsutil/cpu\"\n\t\"github.com/shirou/gopsutil/disk\"\n\t\"github.com/shirou/gopsutil/load\"\n\t\"github.com/shirou/gopsutil/mem\"\n)\n\nconst (\n\tB  = 1\n\tKB = 1024 * B\n\tMB = 1024 * KB\n\tGB = 1024 * MB\n)\n\n// HealthCheck shows `OK` as the ping-pong result.\nfunc HealthCheck(c *gin.Context) {\n\tmessage := \"OK\"\n\tc.String(http.StatusOK, \"\\n\"+message)\n}\n\n// DiskCheck checks the disk usage.\nfunc DiskCheck(c *gin.Context) {\n\tu, _ := disk.Usage(\"/\")\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusOK\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n\n// CPUCheck checks the cpu usage.\nfunc CPUCheck(c *gin.Context) {\n\tcores, _ := cpu.Counts(false)\n\n\ta, _ := load.Avg()\n\tl1 := a.Load1\n\tl5 := a.Load5\n\tl15 := a.Load15\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif l5 >= float64(cores-1) {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if l5 >= float64(cores-2) {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Load average: %.2f, %.2f, %.2f | Cores: %d\", text, l1, l5, l15, cores)\n\tc.String(status, \"\\n\"+message)\n}\n\n// RAMCheck checks the disk usage.\nfunc RAMCheck(c *gin.Context) {\n\tu, _ := mem.VirtualMemory()\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n"
  },
  {
    "path": "demo06/handler/user/create.go",
    "content": "package user\n\nimport (\n\t\"fmt\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n)\n\n// Create creates a new user account.\nfunc Create(c *gin.Context) {\n\tvar r CreateRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tadmin2 := c.Param(\"username\")\n\tlog.Infof(\"URL username: %s\", admin2)\n\n\tdesc := c.Query(\"desc\")\n\tlog.Infof(\"URL key param desc: %s\", desc)\n\n\tcontentType := c.GetHeader(\"Content-Type\")\n\tlog.Infof(\"Header Content-Type: %s\", contentType)\n\n\tlog.Debugf(\"username is: [%s], password is [%s]\", r.Username, r.Password)\n\tif r.Username == \"\" {\n\t\tSendResponse(c, errno.New(errno.ErrUserNotFound, fmt.Errorf(\"username can not found in db: xx.xx.xx.xx\")), nil)\n\t\treturn\n\t}\n\n\tif r.Password == \"\" {\n\t\tSendResponse(c, fmt.Errorf(\"password is empty\"), nil)\n\t}\n\n\trsp := CreateResponse{\n\t\tUsername: r.Username,\n\t}\n\n\t// Show the user information.\n\tSendResponse(c, nil, rsp)\n}\n"
  },
  {
    "path": "demo06/handler/user/user.go",
    "content": "package user\n\ntype CreateRequest struct {\n\tUsername string `json:\"username\"`\n\tPassword string `json:\"password\"`\n}\n\ntype CreateResponse struct {\n\tUsername string `json:\"username\"`\n}\n"
  },
  {
    "path": "demo06/main.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"apiserver/config\"\n\t\"apiserver/model\"\n\t\"apiserver/router\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tcfg = pflag.StringP(\"config\", \"c\", \"\", \"apiserver config file path.\")\n)\n\nfunc main() {\n\tpflag.Parse()\n\n\t// init config\n\tif err := config.Init(*cfg); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// init db\n\tmodel.DB.Init()\n\tdefer model.DB.Close()\n\n\t// Set gin mode.\n\tgin.SetMode(viper.GetString(\"runmode\"))\n\n\t// Create the Gin engine.\n\tg := gin.New()\n\n\tmiddlewares := []gin.HandlerFunc{}\n\n\t// Routes.\n\trouter.Load(\n\t\t// Cores.\n\t\tg,\n\n\t\t// Middlwares.\n\t\tmiddlewares...,\n\t)\n\n\t// Ping the server to make sure the router is working.\n\tgo func() {\n\t\tif err := pingServer(); err != nil {\n\t\t\tlog.Fatal(\"The router has no response, or it might took too long to start up.\", err)\n\t\t}\n\t\tlog.Info(\"The router has been deployed successfully.\")\n\t}()\n\n\tlog.Infof(\"Start to listening the incoming requests on http address: %s\", viper.GetString(\"addr\"))\n\tlog.Info(http.ListenAndServe(viper.GetString(\"addr\"), g).Error())\n}\n\n// pingServer pings the http server to make sure the router is working.\nfunc pingServer() error {\n\tfor i := 0; i < viper.GetInt(\"max_ping_count\"); i++ {\n\t\t// Ping the server by sending a GET request to `/health`.\n\t\tresp, err := http.Get(viper.GetString(\"url\") + \"/sd/health\")\n\t\tif err == nil && resp.StatusCode == 200 {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Sleep for a second to continue the next ping.\n\t\tlog.Info(\"Waiting for the router, retry in 1 second.\")\n\t\ttime.Sleep(time.Second)\n\t}\n\treturn errors.New(\"Cannot connect to the router.\")\n}\n"
  },
  {
    "path": "demo06/model/init.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n\t// MySQL driver.\n\t\"github.com/jinzhu/gorm\"\n\t_ \"github.com/jinzhu/gorm/dialects/mysql\"\n)\n\ntype Database struct {\n\tSelf   *gorm.DB\n\tDocker *gorm.DB\n}\n\nvar DB *Database\n\nfunc openDB(username, password, addr, name string) *gorm.DB {\n\tconfig := fmt.Sprintf(\"%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s\",\n\t\tusername,\n\t\tpassword,\n\t\taddr,\n\t\tname,\n\t\ttrue,\n\t\t//\"Asia/Shanghai\"),\n\t\t\"Local\")\n\n\tdb, err := gorm.Open(\"mysql\", config)\n\tif err != nil {\n\t\tlog.Errorf(err, \"Database connection failed. Database name: %s\", name)\n\t}\n\n\t// set for db connection\n\tsetupDB(db)\n\n\treturn db\n}\n\nfunc setupDB(db *gorm.DB) {\n\tdb.LogMode(viper.GetBool(\"gormlog\"))\n\t//db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数，默认值为0表示不限制.设置最大的连接数，可以避免并发太高导致连接mysql出现too many connections的错误。\n\tdb.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。\n}\n\n// used for cli\nfunc InitSelfDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"db.username\"),\n\t\tviper.GetString(\"db.password\"),\n\t\tviper.GetString(\"db.addr\"),\n\t\tviper.GetString(\"db.name\"))\n}\n\nfunc GetSelfDB() *gorm.DB {\n\treturn InitSelfDB()\n}\n\nfunc InitDockerDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"docker_db.username\"),\n\t\tviper.GetString(\"docker_db.password\"),\n\t\tviper.GetString(\"docker_db.addr\"),\n\t\tviper.GetString(\"docker_db.name\"))\n}\n\nfunc GetDockerDB() *gorm.DB {\n\treturn InitDockerDB()\n}\n\nfunc (db *Database) Init() {\n\tDB = &Database{\n\t\tSelf:   GetSelfDB(),\n\t\tDocker: GetDockerDB(),\n\t}\n}\n\nfunc (db *Database) Close() {\n\tDB.Self.Close()\n\tDB.Docker.Close()\n}\n"
  },
  {
    "path": "demo06/pkg/errno/code.go",
    "content": "package errno\n\nvar (\n\t// Common errors\n\tOK                  = &Errno{Code: 0, Message: \"OK\"}\n\tInternalServerError = &Errno{Code: 10001, Message: \"Internal server error.\"}\n\tErrBind             = &Errno{Code: 10002, Message: \"Error occurred while binding the request body to the struct.\"}\n\n\t// user errors\n\tErrUserNotFound = &Errno{Code: 20102, Message: \"The user was not found.\"}\n)\n"
  },
  {
    "path": "demo06/pkg/errno/errno.go",
    "content": "package errno\n\nimport \"fmt\"\n\ntype Errno struct {\n\tCode    int\n\tMessage string\n}\n\nfunc (err Errno) Error() string {\n\treturn err.Message\n}\n\n// Err represents an error\ntype Err struct {\n\tCode    int\n\tMessage string\n\tErr     error\n}\n\nfunc New(errno *Errno, err error) *Err {\n\treturn &Err{Code: errno.Code, Message: errno.Message, Err: err}\n}\n\nfunc (err *Err) Add(message string) error {\n\t//err.Message = fmt.Sprintf(\"%s %s\", err.Message, message)\n\terr.Message += \" \" + message\n\treturn err\n}\n\nfunc (err *Err) Addf(format string, args ...interface{}) error {\n\t//return err.Message = fmt.Sprintf(\"%s %s\", err.Message, fmt.Sprintf(format, args...))\n\terr.Message += \" \" + fmt.Sprintf(format, args...)\n\treturn err\n}\n\nfunc (err *Err) Error() string {\n\treturn fmt.Sprintf(\"Err - code: %d, message: %s, error: %s\", err.Code, err.Message, err.Err)\n}\n\nfunc IsErrUserNotFound(err error) bool {\n\tcode, _ := DecodeErr(err)\n\treturn code == ErrUserNotFound.Code\n}\n\nfunc DecodeErr(err error) (int, string) {\n\tif err == nil {\n\t\treturn OK.Code, OK.Message\n\t}\n\n\tswitch typed := err.(type) {\n\tcase *Err:\n\t\treturn typed.Code, typed.Message\n\tcase *Errno:\n\t\treturn typed.Code, typed.Message\n\tdefault:\n\t}\n\n\treturn InternalServerError.Code, err.Error()\n}\n"
  },
  {
    "path": "demo06/router/middleware/header.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// NoCache is a middleware function that appends headers\n// to prevent the client from caching the HTTP response.\nfunc NoCache(c *gin.Context) {\n\tc.Header(\"Cache-Control\", \"no-cache, no-store, max-age=0, must-revalidate, value\")\n\tc.Header(\"Expires\", \"Thu, 01 Jan 1970 00:00:00 GMT\")\n\tc.Header(\"Last-Modified\", time.Now().UTC().Format(http.TimeFormat))\n\tc.Next()\n}\n\n// Options is a middleware function that appends headers\n// for options requests and aborts then exits the middleware\n// chain and ends the request.\nfunc Options(c *gin.Context) {\n\tif c.Request.Method != \"OPTIONS\" {\n\t\tc.Next()\n\t} else {\n\t\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\t\tc.Header(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Access-Control-Allow-Headers\", \"authorization, origin, content-type, accept\")\n\t\tc.Header(\"Allow\", \"HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Content-Type\", \"application/json\")\n\t\tc.AbortWithStatus(200)\n\t}\n}\n\n// Secure is a middleware function that appends security\n// and resource access headers.\nfunc Secure(c *gin.Context) {\n\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\tc.Header(\"X-Frame-Options\", \"DENY\")\n\tc.Header(\"X-Content-Type-Options\", \"nosniff\")\n\tc.Header(\"X-XSS-Protection\", \"1; mode=block\")\n\tif c.Request.TLS != nil {\n\t\tc.Header(\"Strict-Transport-Security\", \"max-age=31536000\")\n\t}\n\n\t// Also consider adding Content-Security-Policy headers\n\t// c.Header(\"Content-Security-Policy\", \"script-src 'self' https://cdnjs.cloudflare.com\")\n}\n"
  },
  {
    "path": "demo06/router/router.go",
    "content": "package router\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/handler/sd\"\n\t\"apiserver/handler/user\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Load loads the middlewares, routes, handlers.\nfunc Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {\n\t// Middlewares.\n\tg.Use(gin.Recovery())\n\tg.Use(middleware.NoCache)\n\tg.Use(middleware.Options)\n\tg.Use(middleware.Secure)\n\tg.Use(mw...)\n\t// 404 Handler.\n\tg.NoRoute(func(c *gin.Context) {\n\t\tc.String(http.StatusNotFound, \"The incorrect API route.\")\n\t})\n\n\tu := g.Group(\"/v1/user\")\n\t{\n\t\tu.POST(\"/:username\", user.Create)\n\t}\n\n\t// The health check handlers\n\tsvcd := g.Group(\"/sd\")\n\t{\n\t\tsvcd.GET(\"/health\", sd.HealthCheck)\n\t\tsvcd.GET(\"/disk\", sd.DiskCheck)\n\t\tsvcd.GET(\"/cpu\", sd.CPUCheck)\n\t\tsvcd.GET(\"/ram\", sd.RAMCheck)\n\t}\n\n\treturn g\n}\n"
  },
  {
    "path": "demo07/README.md",
    "content": "实战：用户业务逻辑处理（业务处理）\n"
  },
  {
    "path": "demo07/conf/config.yaml",
    "content": "runmode: debug                 # 开发模式, debug, release, test\naddr: :8080                  # HTTP绑定端口\nname: apiserver              # API Server的名字\nurl: http://127.0.0.1:8080   # pingServer函数请求的API服务器的ip:port\nmax_ping_count: 10           # pingServer函数try的次数\nlog:\n  writers: file,stdout\n  logger_level: DEBUG\n  logger_file: log/apiserver.log\n  log_format_text: false\n  rollingPolicy: size\n  log_rotate_date: 1\n  log_rotate_size: 1\n  log_backup_count: 7\ndb:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\ndocker_db:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\n"
  },
  {
    "path": "demo07/config/config.go",
    "content": "package config\n\nimport (\n\t\"strings\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n)\n\ntype Config struct {\n\tName string\n}\n\nfunc Init(cfg string) error {\n\tc := Config{\n\t\tName: cfg,\n\t}\n\n\t// 初始化配置文件\n\tif err := c.initConfig(); err != nil {\n\t\treturn err\n\t}\n\n\t// 初始化日志包\n\tc.initLog()\n\n\t// 监控配置文件变化并热加载程序\n\tc.watchConfig()\n\n\treturn nil\n}\n\nfunc (c *Config) initConfig() error {\n\tif c.Name != \"\" {\n\t\tviper.SetConfigFile(c.Name) // 如果指定了配置文件，则解析指定的配置文件\n\t} else {\n\t\tviper.AddConfigPath(\"conf\") // 如果没有指定配置文件，则解析默认的配置文件\n\t\tviper.SetConfigName(\"config\")\n\t}\n\tviper.SetConfigType(\"yaml\")     // 设置配置文件格式为YAML\n\tviper.AutomaticEnv()            // 读取匹配的环境变量\n\tviper.SetEnvPrefix(\"APISERVER\") // 读取环境变量的前缀为APISERVER\n\treplacer := strings.NewReplacer(\".\", \"_\")\n\tviper.SetEnvKeyReplacer(replacer)\n\tif err := viper.ReadInConfig(); err != nil { // viper解析配置文件\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *Config) initLog() {\n\tpassLagerCfg := log.PassLagerCfg{\n\t\tWriters:        viper.GetString(\"log.writers\"),\n\t\tLoggerLevel:    viper.GetString(\"log.logger_level\"),\n\t\tLoggerFile:     viper.GetString(\"log.logger_file\"),\n\t\tLogFormatText:  viper.GetBool(\"log.log_format_text\"),\n\t\tRollingPolicy:  viper.GetString(\"log.rollingPolicy\"),\n\t\tLogRotateDate:  viper.GetInt(\"log.log_rotate_date\"),\n\t\tLogRotateSize:  viper.GetInt(\"log.log_rotate_size\"),\n\t\tLogBackupCount: viper.GetInt(\"log.log_backup_count\"),\n\t}\n\n\tlog.InitWithConfig(&passLagerCfg)\n}\n\n// 监控配置文件变化并热加载程序\nfunc (c *Config) watchConfig() {\n\tviper.WatchConfig()\n\tviper.OnConfigChange(func(e fsnotify.Event) {\n\t\tlog.Infof(\"Config file changed: %s\", e.Name)\n\t})\n}\n"
  },
  {
    "path": "demo07/db.sql",
    "content": "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n/*!40101 SET NAMES utf8 */;\n/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;\n/*!40103 SET TIME_ZONE='+00:00' */;\n/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;\n/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n\nCREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */;\n\nUSE `db_apiserver`;\n\n--\n-- Table structure for table `tb_users`\n--\n\nDROP TABLE IF EXISTS `tb_users`;\n/*!40101 SET @saved_cs_client     = @@character_set_client */;\n/*!40101 SET character_set_client = utf8 */;\nCREATE TABLE `tb_users` (\n  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n  `username` varchar(255) NOT NULL,\n  `password` varchar(255) NOT NULL,\n  `createdAt` timestamp NULL DEFAULT NULL,\n  `updatedAt` timestamp NULL DEFAULT NULL,\n  `deletedAt` timestamp NULL DEFAULT NULL,\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `username` (`username`),\n  KEY `idx_tb_users_deletedAt` (`deletedAt`)\n) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;\n/*!40101 SET character_set_client = @saved_cs_client */;\n\n--\n-- Dumping data for table `tb_users`\n--\n\nLOCK TABLES `tb_users` WRITE;\n/*!40000 ALTER TABLE `tb_users` DISABLE KEYS */;\nINSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL);\n/*!40000 ALTER TABLE `tb_users` ENABLE KEYS */;\nUNLOCK TABLES;\n/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;\n\n/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;\n/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;\n/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n\n-- Dump completed on 2018-05-28  0:25:41\n"
  },
  {
    "path": "demo07/handler/handler.go",
    "content": "package handler\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype Response struct {\n\tCode    int         `json:\"code\"`\n\tMessage string      `json:\"message\"`\n\tData    interface{} `json:\"data\"`\n}\n\nfunc SendResponse(c *gin.Context, err error, data interface{}) {\n\tcode, message := errno.DecodeErr(err)\n\n\t// always return http.StatusOK\n\tc.JSON(http.StatusOK, Response{\n\t\tCode:    code,\n\t\tMessage: message,\n\t\tData:    data,\n\t})\n}\n"
  },
  {
    "path": "demo07/handler/sd/check.go",
    "content": "package sd\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/shirou/gopsutil/cpu\"\n\t\"github.com/shirou/gopsutil/disk\"\n\t\"github.com/shirou/gopsutil/load\"\n\t\"github.com/shirou/gopsutil/mem\"\n)\n\nconst (\n\tB  = 1\n\tKB = 1024 * B\n\tMB = 1024 * KB\n\tGB = 1024 * MB\n)\n\n// HealthCheck shows `OK` as the ping-pong result.\nfunc HealthCheck(c *gin.Context) {\n\tmessage := \"OK\"\n\tc.String(http.StatusOK, \"\\n\"+message)\n}\n\n// DiskCheck checks the disk usage.\nfunc DiskCheck(c *gin.Context) {\n\tu, _ := disk.Usage(\"/\")\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusOK\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n\n// CPUCheck checks the cpu usage.\nfunc CPUCheck(c *gin.Context) {\n\tcores, _ := cpu.Counts(false)\n\n\ta, _ := load.Avg()\n\tl1 := a.Load1\n\tl5 := a.Load5\n\tl15 := a.Load15\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif l5 >= float64(cores-1) {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if l5 >= float64(cores-2) {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Load average: %.2f, %.2f, %.2f | Cores: %d\", text, l1, l5, l15, cores)\n\tc.String(status, \"\\n\"+message)\n}\n\n// RAMCheck checks the disk usage.\nfunc RAMCheck(c *gin.Context) {\n\tu, _ := mem.VirtualMemory()\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n"
  },
  {
    "path": "demo07/handler/user/create.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// Create creates a new user account.\nfunc Create(c *gin.Context) {\n\tlog.Info(\"User Create function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\tvar r CreateRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tu := model.UserModel{\n\t\tUsername: r.Username,\n\t\tPassword: r.Password,\n\t}\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\t// Insert the user to the database.\n\tif err := u.Create(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\trsp := CreateResponse{\n\t\tUsername: r.Username,\n\t}\n\n\t// Show the user information.\n\tSendResponse(c, nil, rsp)\n}\n"
  },
  {
    "path": "demo07/handler/user/delete.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Delete delete an user by the user identifier.\nfunc Delete(c *gin.Context) {\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\tif err := model.DeleteUser(uint64(userId)); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo07/handler/user/get.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Get gets an user by the user identifier.\nfunc Get(c *gin.Context) {\n\tusername := c.Param(\"username\")\n\t// Get the user by the `username` from the database.\n\tuser, err := model.GetUser(username)\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrUserNotFound, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, user)\n}\n"
  },
  {
    "path": "demo07/handler/user/list.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/service\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// List list the users in the database.\nfunc List(c *gin.Context) {\n\tvar r ListRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tinfos, count, err := service.ListUser(r.Username, r.Offset, r.Limit)\n\tif err != nil {\n\t\tSendResponse(c, err, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, ListResponse{\n\t\tTotalCount: count,\n\t\tUserList:   infos,\n\t})\n}\n"
  },
  {
    "path": "demo07/handler/user/update.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// Update update a exist user account info.\nfunc Update(c *gin.Context) {\n\tlog.Info(\"Update function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\t// Get the user id from the url parameter.\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\n\t// Binding the user data.\n\tvar u model.UserModel\n\tif err := c.Bind(&u); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\t// We update the record based on the user id.\n\tu.Id = uint64(userId)\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\n\t// Save changed fields.\n\tif err := u.Update(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo07/handler/user/user.go",
    "content": "package user\n\nimport (\n\t\"apiserver/model\"\n)\n\ntype CreateRequest struct {\n\tUsername string `json:\"username\"`\n\tPassword string `json:\"password\"`\n}\n\ntype CreateResponse struct {\n\tUsername string `json:\"username\"`\n}\n\ntype ListRequest struct {\n\tUsername string `json:\"username\"`\n\tOffset   int    `json:\"offset\"`\n\tLimit    int    `json:\"limit\"`\n}\n\ntype ListResponse struct {\n\tTotalCount uint64            `json:\"totalCount\"`\n\tUserList   []*model.UserInfo `json:\"userList\"`\n}\n"
  },
  {
    "path": "demo07/main.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"apiserver/config\"\n\t\"apiserver/model\"\n\t\"apiserver/router\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tcfg = pflag.StringP(\"config\", \"c\", \"\", \"apiserver config file path.\")\n)\n\nfunc main() {\n\tpflag.Parse()\n\n\t// init config\n\tif err := config.Init(*cfg); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// init db\n\tmodel.DB.Init()\n\tdefer model.DB.Close()\n\n\t// Set gin mode.\n\tgin.SetMode(viper.GetString(\"runmode\"))\n\n\t// Create the Gin engine.\n\tg := gin.New()\n\n\tmiddlewares := []gin.HandlerFunc{}\n\n\t// Routes.\n\trouter.Load(\n\t\t// Cores.\n\t\tg,\n\n\t\t// Middlwares.\n\t\tmiddlewares...,\n\t)\n\n\t// Ping the server to make sure the router is working.\n\tgo func() {\n\t\tif err := pingServer(); err != nil {\n\t\t\tlog.Fatal(\"The router has no response, or it might took too long to start up.\", err)\n\t\t}\n\t\tlog.Info(\"The router has been deployed successfully.\")\n\t}()\n\n\tlog.Infof(\"Start to listening the incoming requests on http address: %s\", viper.GetString(\"addr\"))\n\tlog.Info(http.ListenAndServe(viper.GetString(\"addr\"), g).Error())\n}\n\n// pingServer pings the http server to make sure the router is working.\nfunc pingServer() error {\n\tfor i := 0; i < viper.GetInt(\"max_ping_count\"); i++ {\n\t\t// Ping the server by sending a GET request to `/health`.\n\t\tresp, err := http.Get(viper.GetString(\"url\") + \"/sd/health\")\n\t\tif err == nil && resp.StatusCode == 200 {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Sleep for a second to continue the next ping.\n\t\tlog.Info(\"Waiting for the router, retry in 1 second.\")\n\t\ttime.Sleep(time.Second)\n\t}\n\treturn errors.New(\"Cannot connect to the router.\")\n}\n"
  },
  {
    "path": "demo07/model/init.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n\t// MySQL driver.\n\t\"github.com/jinzhu/gorm\"\n\t_ \"github.com/jinzhu/gorm/dialects/mysql\"\n)\n\ntype Database struct {\n\tSelf   *gorm.DB\n\tDocker *gorm.DB\n}\n\nvar DB *Database\n\nfunc openDB(username, password, addr, name string) *gorm.DB {\n\tconfig := fmt.Sprintf(\"%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s\",\n\t\tusername,\n\t\tpassword,\n\t\taddr,\n\t\tname,\n\t\ttrue,\n\t\t//\"Asia/Shanghai\"),\n\t\t\"Local\")\n\n\tdb, err := gorm.Open(\"mysql\", config)\n\tif err != nil {\n\t\tlog.Errorf(err, \"Database connection failed. Database name: %s\", name)\n\t}\n\n\t// set for db connection\n\tsetupDB(db)\n\n\treturn db\n}\n\nfunc setupDB(db *gorm.DB) {\n\tdb.LogMode(viper.GetBool(\"gormlog\"))\n\t//db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数，默认值为0表示不限制.设置最大的连接数，可以避免并发太高导致连接mysql出现too many connections的错误。\n\tdb.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。\n}\n\n// used for cli\nfunc InitSelfDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"db.username\"),\n\t\tviper.GetString(\"db.password\"),\n\t\tviper.GetString(\"db.addr\"),\n\t\tviper.GetString(\"db.name\"))\n}\n\nfunc GetSelfDB() *gorm.DB {\n\treturn InitSelfDB()\n}\n\nfunc InitDockerDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"docker_db.username\"),\n\t\tviper.GetString(\"docker_db.password\"),\n\t\tviper.GetString(\"docker_db.addr\"),\n\t\tviper.GetString(\"docker_db.name\"))\n}\n\nfunc GetDockerDB() *gorm.DB {\n\treturn InitDockerDB()\n}\n\nfunc (db *Database) Init() {\n\tDB = &Database{\n\t\tSelf:   GetSelfDB(),\n\t\tDocker: GetDockerDB(),\n\t}\n}\n\nfunc (db *Database) Close() {\n\tDB.Self.Close()\n\tDB.Docker.Close()\n}\n"
  },
  {
    "path": "demo07/model/model.go",
    "content": "package model\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype BaseModel struct {\n\tId        uint64     `gorm:\"primary_key;AUTO_INCREMENT;column:id\" json:\"-\"`\n\tCreatedAt time.Time  `gorm:\"column:createdAt\" json:\"-\"`\n\tUpdatedAt time.Time  `gorm:\"column:updatedAt\" json:\"-\"`\n\tDeletedAt *time.Time `gorm:\"column:deletedAt\" sql:\"index\" json:\"-\"`\n}\n\ntype UserInfo struct {\n\tId        uint64 `json:\"id\"`\n\tUsername  string `json:\"username\"`\n\tSayHello  string `json:\"sayHello\"`\n\tPassword  string `json:\"password\"`\n\tCreatedAt string `json:\"createdAt\"`\n\tUpdatedAt string `json:\"updatedAt\"`\n}\n\ntype UserList struct {\n\tLock  *sync.Mutex\n\tIdMap map[uint64]*UserInfo\n}\n\n// Token represents a JSON web token.\ntype Token struct {\n\tToken string `json:\"token\"`\n}\n"
  },
  {
    "path": "demo07/model/user.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"apiserver/pkg/auth\"\n\t\"apiserver/pkg/constvar\"\n\n\tvalidator \"gopkg.in/go-playground/validator.v9\"\n)\n\n// User represents a registered user.\ntype UserModel struct {\n\tBaseModel\n\tUsername string `json:\"username\" gorm:\"column:username;not null\" binding:\"required\" validate:\"min=1,max=32\"`\n\tPassword string `json:\"password\" gorm:\"column:password;not null\" binding:\"required\" validate:\"min=5,max=128\"`\n}\n\nfunc (c *UserModel) TableName() string {\n\treturn \"tb_users\"\n}\n\n// Create creates a new user account.\nfunc (u *UserModel) Create() error {\n\treturn DB.Self.Create(&u).Error\n}\n\n// DeleteUser deletes the user by the user identifier.\nfunc DeleteUser(id uint64) error {\n\tuser := UserModel{}\n\tuser.BaseModel.Id = id\n\treturn DB.Self.Delete(&user).Error\n}\n\n// Update updates an user account information.\nfunc (u *UserModel) Update() error {\n\treturn DB.Self.Save(u).Error\n}\n\n// GetUser gets an user by the user identifier.\nfunc GetUser(username string) (*UserModel, error) {\n\tu := &UserModel{}\n\td := DB.Self.Where(\"username = ?\", username).First(&u)\n\treturn u, d.Error\n}\n\n// ListUser List all users\nfunc ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) {\n\tif limit == 0 {\n\t\tlimit = constvar.DefaultLimit\n\t}\n\n\tusers := make([]*UserModel, 0)\n\tvar count uint64\n\n\twhere := fmt.Sprintf(\"username like '%%%s%%'\", username)\n\tif err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\tif err := DB.Self.Where(where).Offset(offset).Limit(limit).Order(\"id desc\").Find(&users).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\treturn users, count, nil\n}\n\n// Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct).\nfunc (u *UserModel) Compare(pwd string) (err error) {\n\terr = auth.Compare(u.Password, pwd)\n\treturn\n}\n\n// Encrypt the user password.\nfunc (u *UserModel) Encrypt() (err error) {\n\tu.Password, err = auth.Encrypt(u.Password)\n\treturn\n}\n\n// Validate the fields.\nfunc (u *UserModel) Validate() error {\n\tvalidate := validator.New()\n\treturn validate.Struct(u)\n}\n"
  },
  {
    "path": "demo07/pkg/auth/auth.go",
    "content": "package auth\n\nimport \"golang.org/x/crypto/bcrypt\"\n\n// Encrypt encrypts the plain text with bcrypt.\nfunc Encrypt(source string) (string, error) {\n\thashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost)\n\treturn string(hashedBytes), err\n}\n\n// Compare compares the encrypted text with the plain text if it's the same.\nfunc Compare(hashedPassword, password string) error {\n\treturn bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))\n}\n"
  },
  {
    "path": "demo07/pkg/constvar/constvar.go",
    "content": "package constvar\n\nconst (\n\tDefaultLimit = 50\n)\n"
  },
  {
    "path": "demo07/pkg/errno/code.go",
    "content": "package errno\n\nvar (\n\t// Common errors\n\tOK                  = &Errno{Code: 0, Message: \"OK\"}\n\tInternalServerError = &Errno{Code: 10001, Message: \"Internal server error\"}\n\tErrBind             = &Errno{Code: 10002, Message: \"Error occurred while binding the request body to the struct.\"}\n\n\tErrValidation = &Errno{Code: 20001, Message: \"Validation failed.\"}\n\tErrDatabase   = &Errno{Code: 20002, Message: \"Database error.\"}\n\tErrToken      = &Errno{Code: 20003, Message: \"Error occurred while signing the JSON web token.\"}\n\n\t// user errors\n\tErrEncrypt           = &Errno{Code: 20101, Message: \"Error occurred while encrypting the user password.\"}\n\tErrUserNotFound      = &Errno{Code: 20102, Message: \"The user was not found.\"}\n\tErrTokenInvalid      = &Errno{Code: 20103, Message: \"The token was invalid.\"}\n\tErrPasswordIncorrect = &Errno{Code: 20104, Message: \"The password was incorrect.\"}\n)\n"
  },
  {
    "path": "demo07/pkg/errno/errno.go",
    "content": "package errno\n\nimport \"fmt\"\n\ntype Errno struct {\n\tCode    int\n\tMessage string\n}\n\nfunc (err Errno) Error() string {\n\treturn err.Message\n}\n\n// Err represents an error\ntype Err struct {\n\tCode    int\n\tMessage string\n\tErr     error\n}\n\nfunc New(errno *Errno, err error) *Err {\n\treturn &Err{Code: errno.Code, Message: errno.Message, Err: err}\n}\n\nfunc (err *Err) Add(message string) error {\n\terr.Message += \" \" + message\n\treturn err\n}\n\nfunc (err *Err) Addf(format string, args ...interface{}) error {\n\terr.Message += \" \" + fmt.Sprintf(format, args...)\n\treturn err\n}\n\nfunc (err *Err) Error() string {\n\treturn fmt.Sprintf(\"Err - code: %d, message: %s, error: %s\", err.Code, err.Message, err.Err)\n}\n\nfunc IsErrUserNotFound(err error) bool {\n\tcode, _ := DecodeErr(err)\n\treturn code == ErrUserNotFound.Code\n}\n\nfunc DecodeErr(err error) (int, string) {\n\tif err == nil {\n\t\treturn OK.Code, OK.Message\n\t}\n\n\tswitch typed := err.(type) {\n\tcase *Err:\n\t\treturn typed.Code, typed.Message\n\tcase *Errno:\n\t\treturn typed.Code, typed.Message\n\tdefault:\n\t}\n\n\treturn InternalServerError.Code, err.Error()\n}\n"
  },
  {
    "path": "demo07/router/middleware/header.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// NoCache is a middleware function that appends headers\n// to prevent the client from caching the HTTP response.\nfunc NoCache(c *gin.Context) {\n\tc.Header(\"Cache-Control\", \"no-cache, no-store, max-age=0, must-revalidate, value\")\n\tc.Header(\"Expires\", \"Thu, 01 Jan 1970 00:00:00 GMT\")\n\tc.Header(\"Last-Modified\", time.Now().UTC().Format(http.TimeFormat))\n\tc.Next()\n}\n\n// Options is a middleware function that appends headers\n// for options requests and aborts then exits the middleware\n// chain and ends the request.\nfunc Options(c *gin.Context) {\n\tif c.Request.Method != \"OPTIONS\" {\n\t\tc.Next()\n\t} else {\n\t\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\t\tc.Header(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Access-Control-Allow-Headers\", \"authorization, origin, content-type, accept\")\n\t\tc.Header(\"Allow\", \"HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Content-Type\", \"application/json\")\n\t\tc.AbortWithStatus(200)\n\t}\n}\n\n// Secure is a middleware function that appends security\n// and resource access headers.\nfunc Secure(c *gin.Context) {\n\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\tc.Header(\"X-Frame-Options\", \"DENY\")\n\tc.Header(\"X-Content-Type-Options\", \"nosniff\")\n\tc.Header(\"X-XSS-Protection\", \"1; mode=block\")\n\tif c.Request.TLS != nil {\n\t\tc.Header(\"Strict-Transport-Security\", \"max-age=31536000\")\n\t}\n\n\t// Also consider adding Content-Security-Policy headers\n\t// c.Header(\"Content-Security-Policy\", \"script-src 'self' https://cdnjs.cloudflare.com\")\n}\n"
  },
  {
    "path": "demo07/router/router.go",
    "content": "package router\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/handler/sd\"\n\t\"apiserver/handler/user\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Load loads the middlewares, routes, handlers.\nfunc Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {\n\t// Middlewares.\n\tg.Use(gin.Recovery())\n\tg.Use(middleware.NoCache)\n\tg.Use(middleware.Options)\n\tg.Use(middleware.Secure)\n\tg.Use(mw...)\n\t// 404 Handler.\n\tg.NoRoute(func(c *gin.Context) {\n\t\tc.String(http.StatusNotFound, \"The incorrect API route.\")\n\t})\n\n\tu := g.Group(\"/v1/user\")\n\t{\n\t\tu.POST(\"\", user.Create)\n\t\tu.DELETE(\"/:id\", user.Delete)\n\t\tu.PUT(\"/:id\", user.Update)\n\t\tu.GET(\"\", user.List)\n\t\tu.GET(\"/:username\", user.Get)\n\t}\n\n\t// The health check handlers\n\tsvcd := g.Group(\"/sd\")\n\t{\n\t\tsvcd.GET(\"/health\", sd.HealthCheck)\n\t\tsvcd.GET(\"/disk\", sd.DiskCheck)\n\t\tsvcd.GET(\"/cpu\", sd.CPUCheck)\n\t\tsvcd.GET(\"/ram\", sd.RAMCheck)\n\t}\n\n\treturn g\n}\n"
  },
  {
    "path": "demo07/service/service.go",
    "content": "package service\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\n\t\"apiserver/model\"\n\t\"apiserver/util\"\n)\n\nfunc ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) {\n\tinfos := make([]*model.UserInfo, 0)\n\tusers, count, err := model.ListUser(username, offset, limit)\n\tif err != nil {\n\t\treturn nil, count, err\n\t}\n\n\tids := []uint64{}\n\tfor _, user := range users {\n\t\tids = append(ids, user.Id)\n\t}\n\n\twg := sync.WaitGroup{}\n\tuserList := model.UserList{\n\t\tLock:  new(sync.Mutex),\n\t\tIdMap: make(map[uint64]*model.UserInfo, len(users)),\n\t}\n\n\terrChan := make(chan error, 1)\n\tfinished := make(chan bool, 1)\n\n\t// Improve query efficiency in parallel\n\tfor _, u := range users {\n\t\twg.Add(1)\n\t\tgo func(u *model.UserModel) {\n\t\t\tdefer wg.Done()\n\n\t\t\tshortId, err := util.GenShortId()\n\t\t\tif err != nil {\n\t\t\t\terrChan <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tuserList.Lock.Lock()\n\t\t\tdefer userList.Lock.Unlock()\n\t\t\tuserList.IdMap[u.Id] = &model.UserInfo{\n\t\t\t\tId:        u.Id,\n\t\t\t\tUsername:  u.Username,\n\t\t\t\tSayHello:  fmt.Sprintf(\"Hello %s\", shortId),\n\t\t\t\tPassword:  u.Password,\n\t\t\t\tCreatedAt: u.CreatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t\tUpdatedAt: u.UpdatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t}\n\t\t}(u)\n\t}\n\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(finished)\n\t}()\n\n\tselect {\n\tcase <-finished:\n\tcase err := <-errChan:\n\t\treturn nil, count, err\n\t}\n\n\tfor _, id := range ids {\n\t\tinfos = append(infos, userList.IdMap[id])\n\t}\n\n\treturn infos, count, nil\n}\n"
  },
  {
    "path": "demo07/util/util.go",
    "content": "package util\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/teris-io/shortid\"\n)\n\nfunc GenShortId() (string, error) {\n\treturn shortid.Generate()\n}\n\nfunc GetReqID(c *gin.Context) string {\n\tv, ok := c.Get(\"X-Request-Id\")\n\tif !ok {\n\t\treturn \"\"\n\t}\n\tif requestId, ok := v.(string); ok {\n\t\treturn requestId\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "demo08/README.md",
    "content": "实战：HTTP调用添加自定义处理逻辑\n"
  },
  {
    "path": "demo08/conf/config.yaml",
    "content": "runmode: debug                 # 开发模式, debug, release, test\naddr: :8080                  # HTTP绑定端口\nname: apiserver              # API Server的名字\nurl: http://127.0.0.1:8080   # pingServer函数请求的API服务器的ip:port\nmax_ping_count: 10           # pingServer函数try的次数\nlog:\n  writers: file,stdout\n  logger_level: DEBUG\n  logger_file: log/apiserver.log\n  log_format_text: false\n  rollingPolicy: size\n  log_rotate_date: 1\n  log_rotate_size: 1\n  log_backup_count: 7\ndb:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\ndocker_db:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\n"
  },
  {
    "path": "demo08/config/config.go",
    "content": "package config\n\nimport (\n\t\"strings\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n)\n\ntype Config struct {\n\tName string\n}\n\nfunc Init(cfg string) error {\n\tc := Config{\n\t\tName: cfg,\n\t}\n\n\t// 初始化配置文件\n\tif err := c.initConfig(); err != nil {\n\t\treturn err\n\t}\n\n\t// 初始化日志包\n\tc.initLog()\n\n\t// 监控配置文件变化并热加载程序\n\tc.watchConfig()\n\n\treturn nil\n}\n\nfunc (c *Config) initConfig() error {\n\tif c.Name != \"\" {\n\t\tviper.SetConfigFile(c.Name) // 如果指定了配置文件，则解析指定的配置文件\n\t} else {\n\t\tviper.AddConfigPath(\"conf\") // 如果没有指定配置文件，则解析默认的配置文件\n\t\tviper.SetConfigName(\"config\")\n\t}\n\tviper.SetConfigType(\"yaml\")     // 设置配置文件格式为YAML\n\tviper.AutomaticEnv()            // 读取匹配的环境变量\n\tviper.SetEnvPrefix(\"APISERVER\") // 读取环境变量的前缀为APISERVER\n\treplacer := strings.NewReplacer(\".\", \"_\")\n\tviper.SetEnvKeyReplacer(replacer)\n\tif err := viper.ReadInConfig(); err != nil { // viper解析配置文件\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *Config) initLog() {\n\tpassLagerCfg := log.PassLagerCfg{\n\t\tWriters:        viper.GetString(\"log.writers\"),\n\t\tLoggerLevel:    viper.GetString(\"log.logger_level\"),\n\t\tLoggerFile:     viper.GetString(\"log.logger_file\"),\n\t\tLogFormatText:  viper.GetBool(\"log.log_format_text\"),\n\t\tRollingPolicy:  viper.GetString(\"log.rollingPolicy\"),\n\t\tLogRotateDate:  viper.GetInt(\"log.log_rotate_date\"),\n\t\tLogRotateSize:  viper.GetInt(\"log.log_rotate_size\"),\n\t\tLogBackupCount: viper.GetInt(\"log.log_backup_count\"),\n\t}\n\n\tlog.InitWithConfig(&passLagerCfg)\n}\n\n// 监控配置文件变化并热加载程序\nfunc (c *Config) watchConfig() {\n\tviper.WatchConfig()\n\tviper.OnConfigChange(func(e fsnotify.Event) {\n\t\tlog.Infof(\"Config file changed: %s\", e.Name)\n\t})\n}\n"
  },
  {
    "path": "demo08/db.sql",
    "content": "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n/*!40101 SET NAMES utf8 */;\n/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;\n/*!40103 SET TIME_ZONE='+00:00' */;\n/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;\n/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n\nCREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */;\n\nUSE `db_apiserver`;\n\n--\n-- Table structure for table `tb_users`\n--\n\nDROP TABLE IF EXISTS `tb_users`;\n/*!40101 SET @saved_cs_client     = @@character_set_client */;\n/*!40101 SET character_set_client = utf8 */;\nCREATE TABLE `tb_users` (\n  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n  `username` varchar(255) NOT NULL,\n  `password` varchar(255) NOT NULL,\n  `createdAt` timestamp NULL DEFAULT NULL,\n  `updatedAt` timestamp NULL DEFAULT NULL,\n  `deletedAt` timestamp NULL DEFAULT NULL,\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `username` (`username`),\n  KEY `idx_tb_users_deletedAt` (`deletedAt`)\n) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;\n/*!40101 SET character_set_client = @saved_cs_client */;\n\n--\n-- Dumping data for table `tb_users`\n--\n\nLOCK TABLES `tb_users` WRITE;\n/*!40000 ALTER TABLE `tb_users` DISABLE KEYS */;\nINSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL);\n/*!40000 ALTER TABLE `tb_users` ENABLE KEYS */;\nUNLOCK TABLES;\n/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;\n\n/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;\n/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;\n/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n\n-- Dump completed on 2018-05-28  0:25:41\n"
  },
  {
    "path": "demo08/handler/handler.go",
    "content": "package handler\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype Response struct {\n\tCode    int         `json:\"code\"`\n\tMessage string      `json:\"message\"`\n\tData    interface{} `json:\"data\"`\n}\n\nfunc SendResponse(c *gin.Context, err error, data interface{}) {\n\tcode, message := errno.DecodeErr(err)\n\n\t// always return http.StatusOK\n\tc.JSON(http.StatusOK, Response{\n\t\tCode:    code,\n\t\tMessage: message,\n\t\tData:    data,\n\t})\n}\n"
  },
  {
    "path": "demo08/handler/sd/check.go",
    "content": "package sd\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/shirou/gopsutil/cpu\"\n\t\"github.com/shirou/gopsutil/disk\"\n\t\"github.com/shirou/gopsutil/load\"\n\t\"github.com/shirou/gopsutil/mem\"\n)\n\nconst (\n\tB  = 1\n\tKB = 1024 * B\n\tMB = 1024 * KB\n\tGB = 1024 * MB\n)\n\n// HealthCheck shows `OK` as the ping-pong result.\nfunc HealthCheck(c *gin.Context) {\n\tmessage := \"OK\"\n\tc.String(http.StatusOK, \"\\n\"+message)\n}\n\n// DiskCheck checks the disk usage.\nfunc DiskCheck(c *gin.Context) {\n\tu, _ := disk.Usage(\"/\")\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusOK\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n\n// CPUCheck checks the cpu usage.\nfunc CPUCheck(c *gin.Context) {\n\tcores, _ := cpu.Counts(false)\n\n\ta, _ := load.Avg()\n\tl1 := a.Load1\n\tl5 := a.Load5\n\tl15 := a.Load15\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif l5 >= float64(cores-1) {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if l5 >= float64(cores-2) {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Load average: %.2f, %.2f, %.2f | Cores: %d\", text, l1, l5, l15, cores)\n\tc.String(status, \"\\n\"+message)\n}\n\n// RAMCheck checks the disk usage.\nfunc RAMCheck(c *gin.Context) {\n\tu, _ := mem.VirtualMemory()\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n"
  },
  {
    "path": "demo08/handler/user/create.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// Create creates a new user account.\nfunc Create(c *gin.Context) {\n\tlog.Info(\"User Create function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\tvar r CreateRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tu := model.UserModel{\n\t\tUsername: r.Username,\n\t\tPassword: r.Password,\n\t}\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\t// Insert the user to the database.\n\tif err := u.Create(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\trsp := CreateResponse{\n\t\tUsername: r.Username,\n\t}\n\n\t// Show the user information.\n\tSendResponse(c, nil, rsp)\n}\n"
  },
  {
    "path": "demo08/handler/user/delete.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Delete delete an user by the user identifier.\nfunc Delete(c *gin.Context) {\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\tif err := model.DeleteUser(uint64(userId)); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo08/handler/user/get.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Get gets an user by the user identifier.\nfunc Get(c *gin.Context) {\n\tusername := c.Param(\"username\")\n\t// Get the user by the `username` from the database.\n\tuser, err := model.GetUser(username)\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrUserNotFound, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, user)\n}\n"
  },
  {
    "path": "demo08/handler/user/list.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/service\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// List list the users in the database.\nfunc List(c *gin.Context) {\n\tvar r ListRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tinfos, count, err := service.ListUser(r.Username, r.Offset, r.Limit)\n\tif err != nil {\n\t\tSendResponse(c, err, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, ListResponse{\n\t\tTotalCount: count,\n\t\tUserList:   infos,\n\t})\n}\n"
  },
  {
    "path": "demo08/handler/user/update.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// Update update a exist user account info.\nfunc Update(c *gin.Context) {\n\tlog.Info(\"Update function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\t// Get the user id from the url parameter.\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\n\t// Binding the user data.\n\tvar u model.UserModel\n\tif err := c.Bind(&u); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\t// We update the record based on the user id.\n\tu.Id = uint64(userId)\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\n\t// Save changed fields.\n\tif err := u.Update(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo08/handler/user/user.go",
    "content": "package user\n\nimport (\n\t\"apiserver/model\"\n)\n\ntype CreateRequest struct {\n\tUsername string `json:\"username\"`\n\tPassword string `json:\"password\"`\n}\n\ntype CreateResponse struct {\n\tUsername string `json:\"username\"`\n}\n\ntype ListRequest struct {\n\tUsername string `json:\"username\"`\n\tOffset   int    `json:\"offset\"`\n\tLimit    int    `json:\"limit\"`\n}\n\ntype ListResponse struct {\n\tTotalCount uint64            `json:\"totalCount\"`\n\tUserList   []*model.UserInfo `json:\"userList\"`\n}\n"
  },
  {
    "path": "demo08/main.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"apiserver/config\"\n\t\"apiserver/model\"\n\t\"apiserver/router\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tcfg = pflag.StringP(\"config\", \"c\", \"\", \"apiserver config file path.\")\n)\n\nfunc main() {\n\tpflag.Parse()\n\n\t// init config\n\tif err := config.Init(*cfg); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// init db\n\tmodel.DB.Init()\n\tdefer model.DB.Close()\n\n\t// Set gin mode.\n\tgin.SetMode(viper.GetString(\"runmode\"))\n\n\t// Create the Gin engine.\n\tg := gin.New()\n\n\t// Routes.\n\trouter.Load(\n\t\t// Cores.\n\t\tg,\n\n\t\t// Middlwares.\n\t\tmiddleware.Logging(),\n\t\tmiddleware.RequestId(),\n\t)\n\n\t// Ping the server to make sure the router is working.\n\tgo func() {\n\t\tif err := pingServer(); err != nil {\n\t\t\tlog.Fatal(\"The router has no response, or it might took too long to start up.\", err)\n\t\t}\n\t\tlog.Info(\"The router has been deployed successfully.\")\n\t}()\n\n\tlog.Infof(\"Start to listening the incoming requests on http address: %s\", viper.GetString(\"addr\"))\n\tlog.Info(http.ListenAndServe(viper.GetString(\"addr\"), g).Error())\n}\n\n// pingServer pings the http server to make sure the router is working.\nfunc pingServer() error {\n\tfor i := 0; i < viper.GetInt(\"max_ping_count\"); i++ {\n\t\t// Ping the server by sending a GET request to `/health`.\n\t\tresp, err := http.Get(viper.GetString(\"url\") + \"/sd/health\")\n\t\tif err == nil && resp.StatusCode == 200 {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Sleep for a second to continue the next ping.\n\t\tlog.Info(\"Waiting for the router, retry in 1 second.\")\n\t\ttime.Sleep(time.Second)\n\t}\n\treturn errors.New(\"Cannot connect to the router.\")\n}\n"
  },
  {
    "path": "demo08/model/init.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n\t// MySQL driver.\n\t\"github.com/jinzhu/gorm\"\n\t_ \"github.com/jinzhu/gorm/dialects/mysql\"\n)\n\ntype Database struct {\n\tSelf   *gorm.DB\n\tDocker *gorm.DB\n}\n\nvar DB *Database\n\nfunc openDB(username, password, addr, name string) *gorm.DB {\n\tconfig := fmt.Sprintf(\"%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s\",\n\t\tusername,\n\t\tpassword,\n\t\taddr,\n\t\tname,\n\t\ttrue,\n\t\t//\"Asia/Shanghai\"),\n\t\t\"Local\")\n\n\tdb, err := gorm.Open(\"mysql\", config)\n\tif err != nil {\n\t\tlog.Errorf(err, \"Database connection failed. Database name: %s\", name)\n\t}\n\n\t// set for db connection\n\tsetupDB(db)\n\n\treturn db\n}\n\nfunc setupDB(db *gorm.DB) {\n\tdb.LogMode(viper.GetBool(\"gormlog\"))\n\t//db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数，默认值为0表示不限制.设置最大的连接数，可以避免并发太高导致连接mysql出现too many connections的错误。\n\tdb.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。\n}\n\n// used for cli\nfunc InitSelfDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"db.username\"),\n\t\tviper.GetString(\"db.password\"),\n\t\tviper.GetString(\"db.addr\"),\n\t\tviper.GetString(\"db.name\"))\n}\n\nfunc GetSelfDB() *gorm.DB {\n\treturn InitSelfDB()\n}\n\nfunc InitDockerDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"docker_db.username\"),\n\t\tviper.GetString(\"docker_db.password\"),\n\t\tviper.GetString(\"docker_db.addr\"),\n\t\tviper.GetString(\"docker_db.name\"))\n}\n\nfunc GetDockerDB() *gorm.DB {\n\treturn InitDockerDB()\n}\n\nfunc (db *Database) Init() {\n\tDB = &Database{\n\t\tSelf:   GetSelfDB(),\n\t\tDocker: GetDockerDB(),\n\t}\n}\n\nfunc (db *Database) Close() {\n\tDB.Self.Close()\n\tDB.Docker.Close()\n}\n"
  },
  {
    "path": "demo08/model/model.go",
    "content": "package model\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype BaseModel struct {\n\tId        uint64     `gorm:\"primary_key;AUTO_INCREMENT;column:id\" json:\"-\"`\n\tCreatedAt time.Time  `gorm:\"column:createdAt\" json:\"-\"`\n\tUpdatedAt time.Time  `gorm:\"column:updatedAt\" json:\"-\"`\n\tDeletedAt *time.Time `gorm:\"column:deletedAt\" sql:\"index\" json:\"-\"`\n}\n\ntype UserInfo struct {\n\tId        uint64 `json:\"id\"`\n\tUsername  string `json:\"username\"`\n\tSayHello  string `json:\"sayHello\"`\n\tPassword  string `json:\"password\"`\n\tCreatedAt string `json:\"createdAt\"`\n\tUpdatedAt string `json:\"updatedAt\"`\n}\n\ntype UserList struct {\n\tLock  *sync.Mutex\n\tIdMap map[uint64]*UserInfo\n}\n\n// Token represents a JSON web token.\ntype Token struct {\n\tToken string `json:\"token\"`\n}\n"
  },
  {
    "path": "demo08/model/user.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"apiserver/pkg/auth\"\n\t\"apiserver/pkg/constvar\"\n\n\tvalidator \"gopkg.in/go-playground/validator.v9\"\n)\n\n// User represents a registered user.\ntype UserModel struct {\n\tBaseModel\n\tUsername string `json:\"username\" gorm:\"column:username;not null\" binding:\"required\" validate:\"min=1,max=32\"`\n\tPassword string `json:\"password\" gorm:\"column:password;not null\" binding:\"required\" validate:\"min=5,max=128\"`\n}\n\nfunc (c *UserModel) TableName() string {\n\treturn \"tb_users\"\n}\n\n// Create creates a new user account.\nfunc (u *UserModel) Create() error {\n\treturn DB.Self.Create(&u).Error\n}\n\n// DeleteUser deletes the user by the user identifier.\nfunc DeleteUser(id uint64) error {\n\tuser := UserModel{}\n\tuser.BaseModel.Id = id\n\treturn DB.Self.Delete(&user).Error\n}\n\n// Update updates an user account information.\nfunc (u *UserModel) Update() error {\n\treturn DB.Self.Save(u).Error\n}\n\n// GetUser gets an user by the user identifier.\nfunc GetUser(username string) (*UserModel, error) {\n\tu := &UserModel{}\n\td := DB.Self.Where(\"username = ?\", username).First(&u)\n\treturn u, d.Error\n}\n\n// ListUser List all users\nfunc ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) {\n\tif limit == 0 {\n\t\tlimit = constvar.DefaultLimit\n\t}\n\n\tusers := make([]*UserModel, 0)\n\tvar count uint64\n\n\twhere := fmt.Sprintf(\"username like '%%%s%%'\", username)\n\tif err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\tif err := DB.Self.Where(where).Offset(offset).Limit(limit).Order(\"id desc\").Find(&users).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\treturn users, count, nil\n}\n\n// Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct).\nfunc (u *UserModel) Compare(pwd string) (err error) {\n\terr = auth.Compare(u.Password, pwd)\n\treturn\n}\n\n// Encrypt the user password.\nfunc (u *UserModel) Encrypt() (err error) {\n\tu.Password, err = auth.Encrypt(u.Password)\n\treturn\n}\n\n// Validate the fields.\nfunc (u *UserModel) Validate() error {\n\tvalidate := validator.New()\n\treturn validate.Struct(u)\n}\n"
  },
  {
    "path": "demo08/pkg/auth/auth.go",
    "content": "package auth\n\nimport \"golang.org/x/crypto/bcrypt\"\n\n// Encrypt encrypts the plain text with bcrypt.\nfunc Encrypt(source string) (string, error) {\n\thashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost)\n\treturn string(hashedBytes), err\n}\n\n// Compare compares the encrypted text with the plain text if it's the same.\nfunc Compare(hashedPassword, password string) error {\n\treturn bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))\n}\n"
  },
  {
    "path": "demo08/pkg/constvar/constvar.go",
    "content": "package constvar\n\nconst (\n\tDefaultLimit = 50\n)\n"
  },
  {
    "path": "demo08/pkg/errno/code.go",
    "content": "package errno\n\nvar (\n\t// Common errors\n\tOK                  = &Errno{Code: 0, Message: \"OK\"}\n\tInternalServerError = &Errno{Code: 10001, Message: \"Internal server error\"}\n\tErrBind             = &Errno{Code: 10002, Message: \"Error occurred while binding the request body to the struct.\"}\n\n\tErrValidation = &Errno{Code: 20001, Message: \"Validation failed.\"}\n\tErrDatabase   = &Errno{Code: 20002, Message: \"Database error.\"}\n\tErrToken      = &Errno{Code: 20003, Message: \"Error occurred while signing the JSON web token.\"}\n\n\t// user errors\n\tErrEncrypt           = &Errno{Code: 20101, Message: \"Error occurred while encrypting the user password.\"}\n\tErrUserNotFound      = &Errno{Code: 20102, Message: \"The user was not found.\"}\n\tErrTokenInvalid      = &Errno{Code: 20103, Message: \"The token was invalid.\"}\n\tErrPasswordIncorrect = &Errno{Code: 20104, Message: \"The password was incorrect.\"}\n)\n"
  },
  {
    "path": "demo08/pkg/errno/errno.go",
    "content": "package errno\n\nimport \"fmt\"\n\ntype Errno struct {\n\tCode    int\n\tMessage string\n}\n\nfunc (err Errno) Error() string {\n\treturn err.Message\n}\n\n// Err represents an error\ntype Err struct {\n\tCode    int\n\tMessage string\n\tErr     error\n}\n\nfunc New(errno *Errno, err error) *Err {\n\treturn &Err{Code: errno.Code, Message: errno.Message, Err: err}\n}\n\nfunc (err *Err) Add(message string) error {\n\terr.Message += \" \" + message\n\treturn err\n}\n\nfunc (err *Err) Addf(format string, args ...interface{}) error {\n\terr.Message += \" \" + fmt.Sprintf(format, args...)\n\treturn err\n}\n\nfunc (err *Err) Error() string {\n\treturn fmt.Sprintf(\"Err - code: %d, message: %s, error: %s\", err.Code, err.Message, err.Err)\n}\n\nfunc IsErrUserNotFound(err error) bool {\n\tcode, _ := DecodeErr(err)\n\treturn code == ErrUserNotFound.Code\n}\n\nfunc DecodeErr(err error) (int, string) {\n\tif err == nil {\n\t\treturn OK.Code, OK.Message\n\t}\n\n\tswitch typed := err.(type) {\n\tcase *Err:\n\t\treturn typed.Code, typed.Message\n\tcase *Errno:\n\t\treturn typed.Code, typed.Message\n\tdefault:\n\t}\n\n\treturn InternalServerError.Code, err.Error()\n}\n"
  },
  {
    "path": "demo08/router/middleware/header.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// NoCache is a middleware function that appends headers\n// to prevent the client from caching the HTTP response.\nfunc NoCache(c *gin.Context) {\n\tc.Header(\"Cache-Control\", \"no-cache, no-store, max-age=0, must-revalidate, value\")\n\tc.Header(\"Expires\", \"Thu, 01 Jan 1970 00:00:00 GMT\")\n\tc.Header(\"Last-Modified\", time.Now().UTC().Format(http.TimeFormat))\n\tc.Next()\n}\n\n// Options is a middleware function that appends headers\n// for options requests and aborts then exits the middleware\n// chain and ends the request.\nfunc Options(c *gin.Context) {\n\tif c.Request.Method != \"OPTIONS\" {\n\t\tc.Next()\n\t} else {\n\t\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\t\tc.Header(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Access-Control-Allow-Headers\", \"authorization, origin, content-type, accept\")\n\t\tc.Header(\"Allow\", \"HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Content-Type\", \"application/json\")\n\t\tc.AbortWithStatus(200)\n\t}\n}\n\n// Secure is a middleware function that appends security\n// and resource access headers.\nfunc Secure(c *gin.Context) {\n\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\tc.Header(\"X-Frame-Options\", \"DENY\")\n\tc.Header(\"X-Content-Type-Options\", \"nosniff\")\n\tc.Header(\"X-XSS-Protection\", \"1; mode=block\")\n\tif c.Request.TLS != nil {\n\t\tc.Header(\"Strict-Transport-Security\", \"max-age=31536000\")\n\t}\n\n\t// Also consider adding Content-Security-Policy headers\n\t// c.Header(\"Content-Security-Policy\", \"script-src 'self' https://cdnjs.cloudflare.com\")\n}\n"
  },
  {
    "path": "demo08/router/middleware/logging.go",
    "content": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/willf/pad\"\n)\n\ntype bodyLogWriter struct {\n\tgin.ResponseWriter\n\tbody *bytes.Buffer\n}\n\nfunc (w bodyLogWriter) Write(b []byte) (int, error) {\n\tw.body.Write(b)\n\treturn w.ResponseWriter.Write(b)\n}\n\n// Logging is a middleware function that logs the each request.\nfunc Logging() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tstart := time.Now().UTC()\n\t\tpath := c.Request.URL.Path\n\n\t\treg := regexp.MustCompile(\"(/v1/user|/login)\")\n\t\tif !reg.MatchString(path) {\n\t\t\treturn\n\t\t}\n\n\t\t// Skip for the health check requests.\n\t\tif path == \"/sd/health\" || path == \"/sd/ram\" || path == \"/sd/cpu\" || path == \"/sd/disk\" {\n\t\t\treturn\n\t\t}\n\n\t\t// Read the Body content\n\t\tvar bodyBytes []byte\n\t\tif c.Request.Body != nil {\n\t\t\tbodyBytes, _ = ioutil.ReadAll(c.Request.Body)\n\t\t}\n\n\t\t// Restore the io.ReadCloser to its original state\n\t\tc.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))\n\n\t\t// The basic informations.\n\t\tmethod := c.Request.Method\n\t\tip := c.ClientIP()\n\n\t\t//log.Debugf(\"New request come in, path: %s, Method: %s, body `%s`\", path, method, string(bodyBytes))\n\t\tblw := &bodyLogWriter{\n\t\t\tbody:           bytes.NewBufferString(\"\"),\n\t\t\tResponseWriter: c.Writer,\n\t\t}\n\t\tc.Writer = blw\n\n\t\t// Continue.\n\t\tc.Next()\n\n\t\t// Calculates the latency.\n\t\tend := time.Now().UTC()\n\t\tlatency := end.Sub(start)\n\n\t\tcode, message := -1, \"\"\n\n\t\t// get code and message\n\t\tvar response handler.Response\n\t\tif err := json.Unmarshal(blw.body.Bytes(), &response); err != nil {\n\t\t\tlog.Errorf(err, \"response body can not unmarshal to model.Response struct, body: `%s`\", blw.body.Bytes())\n\t\t\tcode = errno.InternalServerError.Code\n\t\t\tmessage = err.Error()\n\t\t} else {\n\t\t\tcode = response.Code\n\t\t\tmessage = response.Message\n\t\t}\n\n\t\tlog.Infof(\"%-13s | %-12s | %s %s | {code: %d, message: %s}\", latency, ip, pad.Right(method, 5, \"\"), path, code, message)\n\t}\n}\n"
  },
  {
    "path": "demo08/router/middleware/requestid.go",
    "content": "package middleware\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/satori/go.uuid\"\n)\n\nfunc RequestId() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// Check for incoming header, use it if exists\n\t\trequestId := c.Request.Header.Get(\"X-Request-Id\")\n\n\t\t// Create request id with UUID4\n\t\tif requestId == \"\" {\n\t\t\tu4, _ := uuid.NewV4()\n\t\t\trequestId = u4.String()\n\t\t}\n\n\t\t// Expose it for use in the application\n\t\tc.Set(\"X-Request-Id\", requestId)\n\n\t\t// Set X-Request-Id header\n\t\tc.Writer.Header().Set(\"X-Request-Id\", requestId)\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "demo08/router/router.go",
    "content": "package router\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/handler/sd\"\n\t\"apiserver/handler/user\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Load loads the middlewares, routes, handlers.\nfunc Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {\n\t// Middlewares.\n\tg.Use(gin.Recovery())\n\tg.Use(middleware.NoCache)\n\tg.Use(middleware.Options)\n\tg.Use(middleware.Secure)\n\tg.Use(mw...)\n\t// 404 Handler.\n\tg.NoRoute(func(c *gin.Context) {\n\t\tc.String(http.StatusNotFound, \"The incorrect API route.\")\n\t})\n\n\tu := g.Group(\"/v1/user\")\n\t{\n\t\tu.POST(\"\", user.Create)\n\t\tu.DELETE(\"/:id\", user.Delete)\n\t\tu.PUT(\"/:id\", user.Update)\n\t\tu.GET(\"\", user.List)\n\t\tu.GET(\"/:username\", user.Get)\n\t}\n\n\t// The health check handlers\n\tsvcd := g.Group(\"/sd\")\n\t{\n\t\tsvcd.GET(\"/health\", sd.HealthCheck)\n\t\tsvcd.GET(\"/disk\", sd.DiskCheck)\n\t\tsvcd.GET(\"/cpu\", sd.CPUCheck)\n\t\tsvcd.GET(\"/ram\", sd.RAMCheck)\n\t}\n\n\treturn g\n}\n"
  },
  {
    "path": "demo08/service/service.go",
    "content": "package service\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\n\t\"apiserver/model\"\n\t\"apiserver/util\"\n)\n\nfunc ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) {\n\tinfos := make([]*model.UserInfo, 0)\n\tusers, count, err := model.ListUser(username, offset, limit)\n\tif err != nil {\n\t\treturn nil, count, err\n\t}\n\n\tids := []uint64{}\n\tfor _, user := range users {\n\t\tids = append(ids, user.Id)\n\t}\n\n\twg := sync.WaitGroup{}\n\tuserList := model.UserList{\n\t\tLock:  new(sync.Mutex),\n\t\tIdMap: make(map[uint64]*model.UserInfo, len(users)),\n\t}\n\n\terrChan := make(chan error, 1)\n\tfinished := make(chan bool, 1)\n\n\t// Improve query efficiency in parallel\n\tfor _, u := range users {\n\t\twg.Add(1)\n\t\tgo func(u *model.UserModel) {\n\t\t\tdefer wg.Done()\n\n\t\t\tshortId, err := util.GenShortId()\n\t\t\tif err != nil {\n\t\t\t\terrChan <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tuserList.Lock.Lock()\n\t\t\tdefer userList.Lock.Unlock()\n\t\t\tuserList.IdMap[u.Id] = &model.UserInfo{\n\t\t\t\tId:        u.Id,\n\t\t\t\tUsername:  u.Username,\n\t\t\t\tSayHello:  fmt.Sprintf(\"Hello %s\", shortId),\n\t\t\t\tPassword:  u.Password,\n\t\t\t\tCreatedAt: u.CreatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t\tUpdatedAt: u.UpdatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t}\n\t\t}(u)\n\t}\n\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(finished)\n\t}()\n\n\tselect {\n\tcase <-finished:\n\tcase err := <-errChan:\n\t\treturn nil, count, err\n\t}\n\n\tfor _, id := range ids {\n\t\tinfos = append(infos, userList.IdMap[id])\n\t}\n\n\treturn infos, count, nil\n}\n"
  },
  {
    "path": "demo08/util/util.go",
    "content": "package util\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/teris-io/shortid\"\n)\n\nfunc GenShortId() (string, error) {\n\treturn shortid.Generate()\n}\n\nfunc GetReqID(c *gin.Context) string {\n\tv, ok := c.Get(\"X-Request-Id\")\n\tif !ok {\n\t\treturn \"\"\n\t}\n\tif requestId, ok := v.(string); ok {\n\t\treturn requestId\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "demo09/README.md",
    "content": "实战：API身份验证\n"
  },
  {
    "path": "demo09/conf/config.yaml",
    "content": "runmode: debug                 # 开发模式, debug, release, test\naddr: :8080                  # HTTP绑定端口\nname: apiserver              # API Server的名字\nurl: http://127.0.0.1:8080   # pingServer函数请求的API服务器的ip:port\nmax_ping_count: 10           # pingServer函数try的次数\njwt_secret: Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5\nlog:\n  writers: file,stdout\n  logger_level: DEBUG\n  logger_file: log/apiserver.log\n  log_format_text: false\n  rollingPolicy: size\n  log_rotate_date: 1\n  log_rotate_size: 1\n  log_backup_count: 7\ndb:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\ndocker_db:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\n"
  },
  {
    "path": "demo09/config/config.go",
    "content": "package config\n\nimport (\n\t\"strings\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n)\n\ntype Config struct {\n\tName string\n}\n\nfunc Init(cfg string) error {\n\tc := Config{\n\t\tName: cfg,\n\t}\n\n\t// 初始化配置文件\n\tif err := c.initConfig(); err != nil {\n\t\treturn err\n\t}\n\n\t// 初始化日志包\n\tc.initLog()\n\n\t// 监控配置文件变化并热加载程序\n\tc.watchConfig()\n\n\treturn nil\n}\n\nfunc (c *Config) initConfig() error {\n\tif c.Name != \"\" {\n\t\tviper.SetConfigFile(c.Name) // 如果指定了配置文件，则解析指定的配置文件\n\t} else {\n\t\tviper.AddConfigPath(\"conf\") // 如果没有指定配置文件，则解析默认的配置文件\n\t\tviper.SetConfigName(\"config\")\n\t}\n\tviper.SetConfigType(\"yaml\")     // 设置配置文件格式为YAML\n\tviper.AutomaticEnv()            // 读取匹配的环境变量\n\tviper.SetEnvPrefix(\"APISERVER\") // 读取环境变量的前缀为APISERVER\n\treplacer := strings.NewReplacer(\".\", \"_\")\n\tviper.SetEnvKeyReplacer(replacer)\n\tif err := viper.ReadInConfig(); err != nil { // viper解析配置文件\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *Config) initLog() {\n\tpassLagerCfg := log.PassLagerCfg{\n\t\tWriters:        viper.GetString(\"log.writers\"),\n\t\tLoggerLevel:    viper.GetString(\"log.logger_level\"),\n\t\tLoggerFile:     viper.GetString(\"log.logger_file\"),\n\t\tLogFormatText:  viper.GetBool(\"log.log_format_text\"),\n\t\tRollingPolicy:  viper.GetString(\"log.rollingPolicy\"),\n\t\tLogRotateDate:  viper.GetInt(\"log.log_rotate_date\"),\n\t\tLogRotateSize:  viper.GetInt(\"log.log_rotate_size\"),\n\t\tLogBackupCount: viper.GetInt(\"log.log_backup_count\"),\n\t}\n\n\tlog.InitWithConfig(&passLagerCfg)\n}\n\n// 监控配置文件变化并热加载程序\nfunc (c *Config) watchConfig() {\n\tviper.WatchConfig()\n\tviper.OnConfigChange(func(e fsnotify.Event) {\n\t\tlog.Infof(\"Config file changed: %s\", e.Name)\n\t})\n}\n"
  },
  {
    "path": "demo09/db.sql",
    "content": "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n/*!40101 SET NAMES utf8 */;\n/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;\n/*!40103 SET TIME_ZONE='+00:00' */;\n/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;\n/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n\nCREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */;\n\nUSE `db_apiserver`;\n\n--\n-- Table structure for table `tb_users`\n--\n\nDROP TABLE IF EXISTS `tb_users`;\n/*!40101 SET @saved_cs_client     = @@character_set_client */;\n/*!40101 SET character_set_client = utf8 */;\nCREATE TABLE `tb_users` (\n  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n  `username` varchar(255) NOT NULL,\n  `password` varchar(255) NOT NULL,\n  `createdAt` timestamp NULL DEFAULT NULL,\n  `updatedAt` timestamp NULL DEFAULT NULL,\n  `deletedAt` timestamp NULL DEFAULT NULL,\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `username` (`username`),\n  KEY `idx_tb_users_deletedAt` (`deletedAt`)\n) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;\n/*!40101 SET character_set_client = @saved_cs_client */;\n\n--\n-- Dumping data for table `tb_users`\n--\n\nLOCK TABLES `tb_users` WRITE;\n/*!40000 ALTER TABLE `tb_users` DISABLE KEYS */;\nINSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL);\n/*!40000 ALTER TABLE `tb_users` ENABLE KEYS */;\nUNLOCK TABLES;\n/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;\n\n/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;\n/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;\n/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n\n-- Dump completed on 2018-05-28  0:25:41\n"
  },
  {
    "path": "demo09/handler/handler.go",
    "content": "package handler\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype Response struct {\n\tCode    int         `json:\"code\"`\n\tMessage string      `json:\"message\"`\n\tData    interface{} `json:\"data\"`\n}\n\nfunc SendResponse(c *gin.Context, err error, data interface{}) {\n\tcode, message := errno.DecodeErr(err)\n\n\t// always return http.StatusOK\n\tc.JSON(http.StatusOK, Response{\n\t\tCode:    code,\n\t\tMessage: message,\n\t\tData:    data,\n\t})\n}\n"
  },
  {
    "path": "demo09/handler/sd/check.go",
    "content": "package sd\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/shirou/gopsutil/cpu\"\n\t\"github.com/shirou/gopsutil/disk\"\n\t\"github.com/shirou/gopsutil/load\"\n\t\"github.com/shirou/gopsutil/mem\"\n)\n\nconst (\n\tB  = 1\n\tKB = 1024 * B\n\tMB = 1024 * KB\n\tGB = 1024 * MB\n)\n\n// HealthCheck shows `OK` as the ping-pong result.\nfunc HealthCheck(c *gin.Context) {\n\tmessage := \"OK\"\n\tc.String(http.StatusOK, \"\\n\"+message)\n}\n\n// DiskCheck checks the disk usage.\nfunc DiskCheck(c *gin.Context) {\n\tu, _ := disk.Usage(\"/\")\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusOK\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n\n// CPUCheck checks the cpu usage.\nfunc CPUCheck(c *gin.Context) {\n\tcores, _ := cpu.Counts(false)\n\n\ta, _ := load.Avg()\n\tl1 := a.Load1\n\tl5 := a.Load5\n\tl15 := a.Load15\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif l5 >= float64(cores-1) {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if l5 >= float64(cores-2) {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Load average: %.2f, %.2f, %.2f | Cores: %d\", text, l1, l5, l15, cores)\n\tc.String(status, \"\\n\"+message)\n}\n\n// RAMCheck checks the disk usage.\nfunc RAMCheck(c *gin.Context) {\n\tu, _ := mem.VirtualMemory()\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n"
  },
  {
    "path": "demo09/handler/user/create.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// Create creates a new user account.\nfunc Create(c *gin.Context) {\n\tlog.Info(\"User Create function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\tvar r CreateRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tu := model.UserModel{\n\t\tUsername: r.Username,\n\t\tPassword: r.Password,\n\t}\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\t// Insert the user to the database.\n\tif err := u.Create(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\trsp := CreateResponse{\n\t\tUsername: r.Username,\n\t}\n\n\t// Show the user information.\n\tSendResponse(c, nil, rsp)\n}\n"
  },
  {
    "path": "demo09/handler/user/delete.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Delete delete an user by the user identifier.\nfunc Delete(c *gin.Context) {\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\tif err := model.DeleteUser(uint64(userId)); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo09/handler/user/get.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Get gets an user by the user identifier.\nfunc Get(c *gin.Context) {\n\tusername := c.Param(\"username\")\n\t// Get the user by the `username` from the database.\n\tuser, err := model.GetUser(username)\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrUserNotFound, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, user)\n}\n"
  },
  {
    "path": "demo09/handler/user/list.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/service\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// List list the users in the database.\nfunc List(c *gin.Context) {\n\tvar r ListRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tinfos, count, err := service.ListUser(r.Username, r.Offset, r.Limit)\n\tif err != nil {\n\t\tSendResponse(c, err, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, ListResponse{\n\t\tTotalCount: count,\n\t\tUserList:   infos,\n\t})\n}\n"
  },
  {
    "path": "demo09/handler/user/login.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/auth\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/pkg/token\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Login generates the authentication token\n// if the password was matched with the specified account.\nfunc Login(c *gin.Context) {\n\t// Binding the data with the user struct.\n\tvar u model.UserModel\n\tif err := c.Bind(&u); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\t// Get the user information by the login username.\n\td, err := model.GetUser(u.Username)\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrUserNotFound, nil)\n\t\treturn\n\t}\n\n\t// Compare the login password with the user password.\n\tif err := auth.Compare(d.Password, u.Password); err != nil {\n\t\tSendResponse(c, errno.ErrPasswordIncorrect, nil)\n\t\treturn\n\t}\n\n\t// Sign the json web token.\n\tt, err := token.Sign(c, token.Context{ID: d.Id, Username: d.Username}, \"\")\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrToken, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, model.Token{Token: t})\n}\n"
  },
  {
    "path": "demo09/handler/user/update.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// Update update a exist user account info.\nfunc Update(c *gin.Context) {\n\tlog.Info(\"Update function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\t// Get the user id from the url parameter.\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\n\t// Binding the user data.\n\tvar u model.UserModel\n\tif err := c.Bind(&u); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\t// We update the record based on the user id.\n\tu.Id = uint64(userId)\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\n\t// Save changed fields.\n\tif err := u.Update(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo09/handler/user/user.go",
    "content": "package user\n\nimport (\n\t\"apiserver/model\"\n)\n\ntype CreateRequest struct {\n\tUsername string `json:\"username\"`\n\tPassword string `json:\"password\"`\n}\n\ntype CreateResponse struct {\n\tUsername string `json:\"username\"`\n}\n\ntype ListRequest struct {\n\tUsername string `json:\"username\"`\n\tOffset   int    `json:\"offset\"`\n\tLimit    int    `json:\"limit\"`\n}\n\ntype ListResponse struct {\n\tTotalCount uint64            `json:\"totalCount\"`\n\tUserList   []*model.UserInfo `json:\"userList\"`\n}\n"
  },
  {
    "path": "demo09/main.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"apiserver/config\"\n\t\"apiserver/model\"\n\t\"apiserver/router\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tcfg = pflag.StringP(\"config\", \"c\", \"\", \"apiserver config file path.\")\n)\n\nfunc main() {\n\tpflag.Parse()\n\n\t// init config\n\tif err := config.Init(*cfg); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// init db\n\tmodel.DB.Init()\n\tdefer model.DB.Close()\n\n\t// Set gin mode.\n\tgin.SetMode(viper.GetString(\"runmode\"))\n\n\t// Create the Gin engine.\n\tg := gin.New()\n\n\t// Routes.\n\trouter.Load(\n\t\t// Cores.\n\t\tg,\n\n\t\t// Middlwares.\n\t\tmiddleware.Logging(),\n\t\tmiddleware.RequestId(),\n\t)\n\n\t// Ping the server to make sure the router is working.\n\tgo func() {\n\t\tif err := pingServer(); err != nil {\n\t\t\tlog.Fatal(\"The router has no response, or it might took too long to start up.\", err)\n\t\t}\n\t\tlog.Info(\"The router has been deployed successfully.\")\n\t}()\n\n\tlog.Infof(\"Start to listening the incoming requests on http address: %s\", viper.GetString(\"addr\"))\n\tlog.Info(http.ListenAndServe(viper.GetString(\"addr\"), g).Error())\n}\n\n// pingServer pings the http server to make sure the router is working.\nfunc pingServer() error {\n\tfor i := 0; i < viper.GetInt(\"max_ping_count\"); i++ {\n\t\t// Ping the server by sending a GET request to `/health`.\n\t\tresp, err := http.Get(viper.GetString(\"url\") + \"/sd/health\")\n\t\tif err == nil && resp.StatusCode == 200 {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Sleep for a second to continue the next ping.\n\t\tlog.Info(\"Waiting for the router, retry in 1 second.\")\n\t\ttime.Sleep(time.Second)\n\t}\n\treturn errors.New(\"Cannot connect to the router.\")\n}\n"
  },
  {
    "path": "demo09/model/init.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n\t// MySQL driver.\n\t\"github.com/jinzhu/gorm\"\n\t_ \"github.com/jinzhu/gorm/dialects/mysql\"\n)\n\ntype Database struct {\n\tSelf   *gorm.DB\n\tDocker *gorm.DB\n}\n\nvar DB *Database\n\nfunc openDB(username, password, addr, name string) *gorm.DB {\n\tconfig := fmt.Sprintf(\"%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s\",\n\t\tusername,\n\t\tpassword,\n\t\taddr,\n\t\tname,\n\t\ttrue,\n\t\t//\"Asia/Shanghai\"),\n\t\t\"Local\")\n\n\tdb, err := gorm.Open(\"mysql\", config)\n\tif err != nil {\n\t\tlog.Errorf(err, \"Database connection failed. Database name: %s\", name)\n\t}\n\n\t// set for db connection\n\tsetupDB(db)\n\n\treturn db\n}\n\nfunc setupDB(db *gorm.DB) {\n\tdb.LogMode(viper.GetBool(\"gormlog\"))\n\t//db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数，默认值为0表示不限制.设置最大的连接数，可以避免并发太高导致连接mysql出现too many connections的错误。\n\tdb.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。\n}\n\n// used for cli\nfunc InitSelfDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"db.username\"),\n\t\tviper.GetString(\"db.password\"),\n\t\tviper.GetString(\"db.addr\"),\n\t\tviper.GetString(\"db.name\"))\n}\n\nfunc GetSelfDB() *gorm.DB {\n\treturn InitSelfDB()\n}\n\nfunc InitDockerDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"docker_db.username\"),\n\t\tviper.GetString(\"docker_db.password\"),\n\t\tviper.GetString(\"docker_db.addr\"),\n\t\tviper.GetString(\"docker_db.name\"))\n}\n\nfunc GetDockerDB() *gorm.DB {\n\treturn InitDockerDB()\n}\n\nfunc (db *Database) Init() {\n\tDB = &Database{\n\t\tSelf:   GetSelfDB(),\n\t\tDocker: GetDockerDB(),\n\t}\n}\n\nfunc (db *Database) Close() {\n\tDB.Self.Close()\n\tDB.Docker.Close()\n}\n"
  },
  {
    "path": "demo09/model/model.go",
    "content": "package model\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype BaseModel struct {\n\tId        uint64     `gorm:\"primary_key;AUTO_INCREMENT;column:id\" json:\"-\"`\n\tCreatedAt time.Time  `gorm:\"column:createdAt\" json:\"-\"`\n\tUpdatedAt time.Time  `gorm:\"column:updatedAt\" json:\"-\"`\n\tDeletedAt *time.Time `gorm:\"column:deletedAt\" sql:\"index\" json:\"-\"`\n}\n\ntype UserInfo struct {\n\tId        uint64 `json:\"id\"`\n\tUsername  string `json:\"username\"`\n\tSayHello  string `json:\"sayHello\"`\n\tPassword  string `json:\"password\"`\n\tCreatedAt string `json:\"createdAt\"`\n\tUpdatedAt string `json:\"updatedAt\"`\n}\n\ntype UserList struct {\n\tLock  *sync.Mutex\n\tIdMap map[uint64]*UserInfo\n}\n\n// Token represents a JSON web token.\ntype Token struct {\n\tToken string `json:\"token\"`\n}\n"
  },
  {
    "path": "demo09/model/user.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"apiserver/pkg/auth\"\n\t\"apiserver/pkg/constvar\"\n\n\tvalidator \"gopkg.in/go-playground/validator.v9\"\n)\n\n// User represents a registered user.\ntype UserModel struct {\n\tBaseModel\n\tUsername string `json:\"username\" gorm:\"column:username;not null\" binding:\"required\" validate:\"min=1,max=32\"`\n\tPassword string `json:\"password\" gorm:\"column:password;not null\" binding:\"required\" validate:\"min=5,max=128\"`\n}\n\nfunc (c *UserModel) TableName() string {\n\treturn \"tb_users\"\n}\n\n// Create creates a new user account.\nfunc (u *UserModel) Create() error {\n\treturn DB.Self.Create(&u).Error\n}\n\n// DeleteUser deletes the user by the user identifier.\nfunc DeleteUser(id uint64) error {\n\tuser := UserModel{}\n\tuser.BaseModel.Id = id\n\treturn DB.Self.Delete(&user).Error\n}\n\n// Update updates an user account information.\nfunc (u *UserModel) Update() error {\n\treturn DB.Self.Save(u).Error\n}\n\n// GetUser gets an user by the user identifier.\nfunc GetUser(username string) (*UserModel, error) {\n\tu := &UserModel{}\n\td := DB.Self.Where(\"username = ?\", username).First(&u)\n\treturn u, d.Error\n}\n\n// ListUser List all users\nfunc ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) {\n\tif limit == 0 {\n\t\tlimit = constvar.DefaultLimit\n\t}\n\n\tusers := make([]*UserModel, 0)\n\tvar count uint64\n\n\twhere := fmt.Sprintf(\"username like '%%%s%%'\", username)\n\tif err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\tif err := DB.Self.Where(where).Offset(offset).Limit(limit).Order(\"id desc\").Find(&users).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\treturn users, count, nil\n}\n\n// Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct).\nfunc (u *UserModel) Compare(pwd string) (err error) {\n\terr = auth.Compare(u.Password, pwd)\n\treturn\n}\n\n// Encrypt the user password.\nfunc (u *UserModel) Encrypt() (err error) {\n\tu.Password, err = auth.Encrypt(u.Password)\n\treturn\n}\n\n// Validate the fields.\nfunc (u *UserModel) Validate() error {\n\tvalidate := validator.New()\n\treturn validate.Struct(u)\n}\n"
  },
  {
    "path": "demo09/pkg/auth/auth.go",
    "content": "package auth\n\nimport \"golang.org/x/crypto/bcrypt\"\n\n// Encrypt encrypts the plain text with bcrypt.\nfunc Encrypt(source string) (string, error) {\n\thashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost)\n\treturn string(hashedBytes), err\n}\n\n// Compare compares the encrypted text with the plain text if it's the same.\nfunc Compare(hashedPassword, password string) error {\n\treturn bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))\n}\n"
  },
  {
    "path": "demo09/pkg/constvar/constvar.go",
    "content": "package constvar\n\nconst (\n\tDefaultLimit = 50\n)\n"
  },
  {
    "path": "demo09/pkg/errno/code.go",
    "content": "package errno\n\nvar (\n\t// Common errors\n\tOK                  = &Errno{Code: 0, Message: \"OK\"}\n\tInternalServerError = &Errno{Code: 10001, Message: \"Internal server error\"}\n\tErrBind             = &Errno{Code: 10002, Message: \"Error occurred while binding the request body to the struct.\"}\n\n\tErrValidation = &Errno{Code: 20001, Message: \"Validation failed.\"}\n\tErrDatabase   = &Errno{Code: 20002, Message: \"Database error.\"}\n\tErrToken      = &Errno{Code: 20003, Message: \"Error occurred while signing the JSON web token.\"}\n\n\t// user errors\n\tErrEncrypt           = &Errno{Code: 20101, Message: \"Error occurred while encrypting the user password.\"}\n\tErrUserNotFound      = &Errno{Code: 20102, Message: \"The user was not found.\"}\n\tErrTokenInvalid      = &Errno{Code: 20103, Message: \"The token was invalid.\"}\n\tErrPasswordIncorrect = &Errno{Code: 20104, Message: \"The password was incorrect.\"}\n)\n"
  },
  {
    "path": "demo09/pkg/errno/errno.go",
    "content": "package errno\n\nimport \"fmt\"\n\ntype Errno struct {\n\tCode    int\n\tMessage string\n}\n\nfunc (err Errno) Error() string {\n\treturn err.Message\n}\n\n// Err represents an error\ntype Err struct {\n\tCode    int\n\tMessage string\n\tErr     error\n}\n\nfunc New(errno *Errno, err error) *Err {\n\treturn &Err{Code: errno.Code, Message: errno.Message, Err: err}\n}\n\nfunc (err *Err) Add(message string) error {\n\terr.Message += \" \" + message\n\treturn err\n}\n\nfunc (err *Err) Addf(format string, args ...interface{}) error {\n\terr.Message += \" \" + fmt.Sprintf(format, args...)\n\treturn err\n}\n\nfunc (err *Err) Error() string {\n\treturn fmt.Sprintf(\"Err - code: %d, message: %s, error: %s\", err.Code, err.Message, err.Err)\n}\n\nfunc IsErrUserNotFound(err error) bool {\n\tcode, _ := DecodeErr(err)\n\treturn code == ErrUserNotFound.Code\n}\n\nfunc DecodeErr(err error) (int, string) {\n\tif err == nil {\n\t\treturn OK.Code, OK.Message\n\t}\n\n\tswitch typed := err.(type) {\n\tcase *Err:\n\t\treturn typed.Code, typed.Message\n\tcase *Errno:\n\t\treturn typed.Code, typed.Message\n\tdefault:\n\t}\n\n\treturn InternalServerError.Code, err.Error()\n}\n"
  },
  {
    "path": "demo09/pkg/token/token.go",
    "content": "package token\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\tjwt \"github.com/dgrijalva/jwt-go\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\t// ErrMissingHeader means the `Authorization` header was empty.\n\tErrMissingHeader = errors.New(\"The length of the `Authorization` header is zero.\")\n)\n\n// Context is the context of the JSON web token.\ntype Context struct {\n\tID       uint64\n\tUsername string\n}\n\n// secretFunc validates the secret format.\nfunc secretFunc(secret string) jwt.Keyfunc {\n\treturn func(token *jwt.Token) (interface{}, error) {\n\t\t// Make sure the `alg` is what we except.\n\t\tif _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {\n\t\t\treturn nil, jwt.ErrSignatureInvalid\n\t\t}\n\n\t\treturn []byte(secret), nil\n\t}\n}\n\n// Parse validates the token with the specified secret,\n// and returns the context if the token was valid.\nfunc Parse(tokenString string, secret string) (*Context, error) {\n\tctx := &Context{}\n\n\t// Parse the token.\n\ttoken, err := jwt.Parse(tokenString, secretFunc(secret))\n\n\t// Parse error.\n\tif err != nil {\n\t\treturn ctx, err\n\n\t\t// Read the token if it's valid.\n\t} else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {\n\t\tctx.ID = uint64(claims[\"id\"].(float64))\n\t\tctx.Username = claims[\"username\"].(string)\n\t\treturn ctx, nil\n\n\t\t// Other errors.\n\t} else {\n\t\treturn ctx, err\n\t}\n}\n\n// ParseRequest gets the token from the header and\n// pass it to the Parse function to parses the token.\nfunc ParseRequest(c *gin.Context) (*Context, error) {\n\theader := c.Request.Header.Get(\"Authorization\")\n\n\t// Load the jwt secret from config\n\tsecret := viper.GetString(\"jwt_secret\")\n\n\tif len(header) == 0 {\n\t\treturn &Context{}, ErrMissingHeader\n\t}\n\n\tvar t string\n\t// Parse the header to get the token part.\n\tfmt.Sscanf(header, \"Bearer %s\", &t)\n\treturn Parse(t, secret)\n}\n\n// Sign signs the context with the specified secret.\nfunc Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) {\n\t// Load the jwt secret from the Gin config if the secret isn't specified.\n\tif secret == \"\" {\n\t\tsecret = viper.GetString(\"jwt_secret\")\n\t}\n\t// The token content.\n\ttoken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{\n\t\t\"id\":       c.ID,\n\t\t\"username\": c.Username,\n\t\t\"nbf\":      time.Now().Unix(),\n\t\t\"iat\":      time.Now().Unix(),\n\t})\n\t// Sign the token with the specified secret.\n\ttokenString, err = token.SignedString([]byte(secret))\n\n\treturn\n}\n"
  },
  {
    "path": "demo09/router/middleware/auth.go",
    "content": "package middleware\n\nimport (\n\t\"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/pkg/token\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc AuthMiddleware() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// Parse the json web token.\n\t\tif _, err := token.ParseRequest(c); err != nil {\n\t\t\thandler.SendResponse(c, errno.ErrTokenInvalid, nil)\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "demo09/router/middleware/header.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// NoCache is a middleware function that appends headers\n// to prevent the client from caching the HTTP response.\nfunc NoCache(c *gin.Context) {\n\tc.Header(\"Cache-Control\", \"no-cache, no-store, max-age=0, must-revalidate, value\")\n\tc.Header(\"Expires\", \"Thu, 01 Jan 1970 00:00:00 GMT\")\n\tc.Header(\"Last-Modified\", time.Now().UTC().Format(http.TimeFormat))\n\tc.Next()\n}\n\n// Options is a middleware function that appends headers\n// for options requests and aborts then exits the middleware\n// chain and ends the request.\nfunc Options(c *gin.Context) {\n\tif c.Request.Method != \"OPTIONS\" {\n\t\tc.Next()\n\t} else {\n\t\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\t\tc.Header(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Access-Control-Allow-Headers\", \"authorization, origin, content-type, accept\")\n\t\tc.Header(\"Allow\", \"HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Content-Type\", \"application/json\")\n\t\tc.AbortWithStatus(200)\n\t}\n}\n\n// Secure is a middleware function that appends security\n// and resource access headers.\nfunc Secure(c *gin.Context) {\n\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\tc.Header(\"X-Frame-Options\", \"DENY\")\n\tc.Header(\"X-Content-Type-Options\", \"nosniff\")\n\tc.Header(\"X-XSS-Protection\", \"1; mode=block\")\n\tif c.Request.TLS != nil {\n\t\tc.Header(\"Strict-Transport-Security\", \"max-age=31536000\")\n\t}\n\n\t// Also consider adding Content-Security-Policy headers\n\t// c.Header(\"Content-Security-Policy\", \"script-src 'self' https://cdnjs.cloudflare.com\")\n}\n"
  },
  {
    "path": "demo09/router/middleware/logging.go",
    "content": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/willf/pad\"\n)\n\ntype bodyLogWriter struct {\n\tgin.ResponseWriter\n\tbody *bytes.Buffer\n}\n\nfunc (w bodyLogWriter) Write(b []byte) (int, error) {\n\tw.body.Write(b)\n\treturn w.ResponseWriter.Write(b)\n}\n\n// Logging is a middleware function that logs the each request.\nfunc Logging() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tstart := time.Now().UTC()\n\t\tpath := c.Request.URL.Path\n\n\t\treg := regexp.MustCompile(\"(/v1/user|/login)\")\n\t\tif !reg.MatchString(path) {\n\t\t\treturn\n\t\t}\n\n\t\t// Skip for the health check requests.\n\t\tif path == \"/sd/health\" || path == \"/sd/ram\" || path == \"/sd/cpu\" || path == \"/sd/disk\" {\n\t\t\treturn\n\t\t}\n\n\t\t// Read the Body content\n\t\tvar bodyBytes []byte\n\t\tif c.Request.Body != nil {\n\t\t\tbodyBytes, _ = ioutil.ReadAll(c.Request.Body)\n\t\t}\n\n\t\t// Restore the io.ReadCloser to its original state\n\t\tc.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))\n\n\t\t// The basic informations.\n\t\tmethod := c.Request.Method\n\t\tip := c.ClientIP()\n\n\t\t//log.Debugf(\"New request come in, path: %s, Method: %s, body `%s`\", path, method, string(bodyBytes))\n\t\tblw := &bodyLogWriter{\n\t\t\tbody:           bytes.NewBufferString(\"\"),\n\t\t\tResponseWriter: c.Writer,\n\t\t}\n\t\tc.Writer = blw\n\n\t\t// Continue.\n\t\tc.Next()\n\n\t\t// Calculates the latency.\n\t\tend := time.Now().UTC()\n\t\tlatency := end.Sub(start)\n\n\t\tcode, message := -1, \"\"\n\n\t\t// get code and message\n\t\tvar response handler.Response\n\t\tif err := json.Unmarshal(blw.body.Bytes(), &response); err != nil {\n\t\t\tlog.Errorf(err, \"response body can not unmarshal to model.Response struct, body: `%s`\", blw.body.Bytes())\n\t\t\tcode = errno.InternalServerError.Code\n\t\t\tmessage = err.Error()\n\t\t} else {\n\t\t\tcode = response.Code\n\t\t\tmessage = response.Message\n\t\t}\n\n\t\tlog.Infof(\"%-13s | %-12s | %s %s | {code: %d, message: %s}\", latency, ip, pad.Right(method, 5, \"\"), path, code, message)\n\t}\n}\n"
  },
  {
    "path": "demo09/router/middleware/requestid.go",
    "content": "package middleware\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/satori/go.uuid\"\n)\n\nfunc RequestId() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// Check for incoming header, use it if exists\n\t\trequestId := c.Request.Header.Get(\"X-Request-Id\")\n\n\t\t// Create request id with UUID4\n\t\tif requestId == \"\" {\n\t\t\tu4, _ := uuid.NewV4()\n\t\t\trequestId = u4.String()\n\t\t}\n\n\t\t// Expose it for use in the application\n\t\tc.Set(\"X-Request-Id\", requestId)\n\n\t\t// Set X-Request-Id header\n\t\tc.Writer.Header().Set(\"X-Request-Id\", requestId)\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "demo09/router/router.go",
    "content": "package router\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/handler/sd\"\n\t\"apiserver/handler/user\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Load loads the middlewares, routes, handlers.\nfunc Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {\n\t// Middlewares.\n\tg.Use(gin.Recovery())\n\tg.Use(middleware.NoCache)\n\tg.Use(middleware.Options)\n\tg.Use(middleware.Secure)\n\tg.Use(mw...)\n\t// 404 Handler.\n\tg.NoRoute(func(c *gin.Context) {\n\t\tc.String(http.StatusNotFound, \"The incorrect API route.\")\n\t})\n\n\t// api for authentication functionalities\n\tg.POST(\"/login\", user.Login)\n\n\t// The user handlers, requiring authentication\n\tu := g.Group(\"/v1/user\")\n\tu.Use(middleware.AuthMiddleware())\n\t{\n\t\tu.POST(\"\", user.Create)\n\t\tu.DELETE(\"/:id\", user.Delete)\n\t\tu.PUT(\"/:id\", user.Update)\n\t\tu.GET(\"\", user.List)\n\t\tu.GET(\"/:username\", user.Get)\n\t}\n\n\t// The health check handlers\n\tsvcd := g.Group(\"/sd\")\n\t{\n\t\tsvcd.GET(\"/health\", sd.HealthCheck)\n\t\tsvcd.GET(\"/disk\", sd.DiskCheck)\n\t\tsvcd.GET(\"/cpu\", sd.CPUCheck)\n\t\tsvcd.GET(\"/ram\", sd.RAMCheck)\n\t}\n\n\treturn g\n}\n"
  },
  {
    "path": "demo09/service/service.go",
    "content": "package service\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\n\t\"apiserver/model\"\n\t\"apiserver/util\"\n)\n\nfunc ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) {\n\tinfos := make([]*model.UserInfo, 0)\n\tusers, count, err := model.ListUser(username, offset, limit)\n\tif err != nil {\n\t\treturn nil, count, err\n\t}\n\n\tids := []uint64{}\n\tfor _, user := range users {\n\t\tids = append(ids, user.Id)\n\t}\n\n\twg := sync.WaitGroup{}\n\tuserList := model.UserList{\n\t\tLock:  new(sync.Mutex),\n\t\tIdMap: make(map[uint64]*model.UserInfo, len(users)),\n\t}\n\n\terrChan := make(chan error, 1)\n\tfinished := make(chan bool, 1)\n\n\t// Improve query efficiency in parallel\n\tfor _, u := range users {\n\t\twg.Add(1)\n\t\tgo func(u *model.UserModel) {\n\t\t\tdefer wg.Done()\n\n\t\t\tshortId, err := util.GenShortId()\n\t\t\tif err != nil {\n\t\t\t\terrChan <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tuserList.Lock.Lock()\n\t\t\tdefer userList.Lock.Unlock()\n\t\t\tuserList.IdMap[u.Id] = &model.UserInfo{\n\t\t\t\tId:        u.Id,\n\t\t\t\tUsername:  u.Username,\n\t\t\t\tSayHello:  fmt.Sprintf(\"Hello %s\", shortId),\n\t\t\t\tPassword:  u.Password,\n\t\t\t\tCreatedAt: u.CreatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t\tUpdatedAt: u.UpdatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t}\n\t\t}(u)\n\t}\n\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(finished)\n\t}()\n\n\tselect {\n\tcase <-finished:\n\tcase err := <-errChan:\n\t\treturn nil, count, err\n\t}\n\n\tfor _, id := range ids {\n\t\tinfos = append(infos, userList.IdMap[id])\n\t}\n\n\treturn infos, count, nil\n}\n"
  },
  {
    "path": "demo09/util/util.go",
    "content": "package util\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/teris-io/shortid\"\n)\n\nfunc GenShortId() (string, error) {\n\treturn shortid.Generate()\n}\n\nfunc GetReqID(c *gin.Context) string {\n\tv, ok := c.Get(\"X-Request-Id\")\n\tif !ok {\n\t\treturn \"\"\n\t}\n\tif requestId, ok := v.(string); ok {\n\t\treturn requestId\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "demo10/README.md",
    "content": "进阶：用HTTPS加密API请求\n"
  },
  {
    "path": "demo10/conf/config.yaml",
    "content": "runmode: debug                 # 开发模式, debug, release, test\naddr: :8080                  # HTTP绑定端口\nname: apiserver              # API Server的名字\nurl: http://127.0.0.1:8080   # pingServer函数请求的API服务器的ip:port\nmax_ping_count: 10           # pingServer函数try的次数\njwt_secret: Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5\ntls:\n  addr: :8081\n  cert: conf/server.crt\n  key: conf/server.key\nlog:\n  writers: stdout\n  logger_level: DEBUG\n  logger_file: log/apiserver.log\n  log_format_text: true\n  rollingPolicy: size\n  log_rotate_date: 1\n  log_rotate_size: 1\n  log_backup_count: 7\ndb:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\ndocker_db:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\n"
  },
  {
    "path": "demo10/conf/server.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIID2TCCAsGgAwIBAgIJAMQRszo28YxYMA0GCSqGSIb3DQEBBQUAMIGCMQswCQYD\nVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO\nUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAkxMjcuMC4wLjEx\nGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNvbTAeFw0xODA2MDQwNTI5MzdaFw0y\nODA2MDEwNTI5MzdaMIGCMQswCQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYD\nVQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklU\nMRIwEAYDVQQDDAkxMjcuMC4wLjExGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNv\nbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALw6CzJlFxNg79rSbFa/\nQn1IThDOb+MKOOpQjeWCSrKFTrWnU36BouUN7b9VYEn9+GpcFoyaThK5HsJX4wyd\n65yTCct3WieuegdpbttA5w8vcHbUNwvfk9WTBbxXbweZrVajsGRhg1Y+8B2UM/DG\ndzwrkO3woKaTrYlo7kkVFegODUhE7+KkEt/B9hiLf8WWk9fDjh8VVq0mXRQXhuIR\nOfJ1UFb7CEdAoQa99wS0sc2hYW4QAkElgVbCAK+w2H1sPl48xaIVhsgeS/4BR1Om\nEsykygsFtU8jwO4J/c/e8seekNFPssUBc/kFHQXp333H/zMhFLQfYcEZW/wPTV8R\nC2ECAwEAAaNQME4wHQYDVR0OBBYEFCSzcWsMsL3gGOUnlWxSUZZOrud3MB8GA1Ud\nIwQYMBaAFCSzcWsMsL3gGOUnlWxSUZZOrud3MAwGA1UdEwQFMAMBAf8wDQYJKoZI\nhvcNAQEFBQADggEBAK8GS3jy4Og6WHB68BPCc2odBjAlug91otn0PNtpfp431fWw\nbpviOqRtr6aFqJ8SwdVtWynjAXTTeKZNJB0XNf7HnPMC15A+L4MwtUH9SPQ3abeW\nXLQTGl0Q5sgLPR9jADQ3gEgoO4cgKMNy97YhemkQZ1PpVhH9VGObebObcpPRvtQL\n5+TurehVL1hYsrQ5e6q1VUZg/DJuYT096t238vYU5GimLgv8d4GSXA/PbAeTBzd6\n1mofLFAyp2eJkjOZgo8OgNVSQ3aRqUluoD/8qSjnBhr9GoANCuBFJMphqPqMxYtZ\ncBM/rpG8PFiF5gCjeU/6rt+39w9QEST7y583dVk=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "demo10/conf/server.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8OgsyZRcTYO/a\n0mxWv0J9SE4Qzm/jCjjqUI3lgkqyhU61p1N+gaLlDe2/VWBJ/fhqXBaMmk4SuR7C\nV+MMneuckwnLd1onrnoHaW7bQOcPL3B21DcL35PVkwW8V28Hma1Wo7BkYYNWPvAd\nlDPwxnc8K5Dt8KCmk62JaO5JFRXoDg1IRO/ipBLfwfYYi3/FlpPXw44fFVatJl0U\nF4biETnydVBW+whHQKEGvfcEtLHNoWFuEAJBJYFWwgCvsNh9bD5ePMWiFYbIHkv+\nAUdTphLMpMoLBbVPI8DuCf3P3vLHnpDRT7LFAXP5BR0F6d99x/8zIRS0H2HBGVv8\nD01fEQthAgMBAAECggEAVjU/a5VhPD7pnA9ED3cJvNeg6ZGjLRlBeA/s7XD/RURJ\nCGnak9ZMBRycB0XTFBB99ji3Gy6RE4I11EzscJrjjpLJqabAY+xFd5+SZlkTeqD/\noW0QyR9dVjRALELfV1vLSCMwZslCnf21e9ak82Hyulw5xMCw05pPoN+uQ0qk/eKn\niOPnWhMLg2o2XX1C4ScYOVF0lY9RVyOIGEl6pW97QOlNM3M2D02iUytgg44Y0DSU\nq2vo2dcroNGzr8vEb1JzFbiXBeG1EBKWhB9boswhZ78Wer+KWwWYaoJcK6BgHRq+\nMPRU36LarxLT47reoq1yeN5fM3NmaYiGKroHbGPwAQKBgQDzipn70+HIOU+vXyoa\n6eQJDOfTgXjtEh1jbozus+PIgYo1ciFo9awmRGvDelItF2zOtToCU7AmzM/XNhiU\nQNQvoV5fYRUfou0wU9m/BtiOi2rzOWgZmPsuc4tO5ptk7lNuqfaJ9b3pH0ie4UgO\nWA0U2gwvZOG9hUbJU5YaFpwbCQKBgQDF2wuzql8sb/Ml6G2sGqg6RvRyO1N71hS3\noqmLxY/2Nr9RO6KvIGw1tAMoxPAyB/erjs0X5UVF3XuKE4bmZMqHBrsVe3f8SA97\nigk9gFGTGI51tiLADnLp8uXKW1/Wd6GwhM1fxPagiASExhNU/TwZgmwGdDMMyeMW\nUJdW+x+LmQKBgQCVVqljxaKOv64AUO+lv0SI1DQX+y2m2dPRlAmxmfeUjPKuIUUh\ncnxUnuIh5REc+19KRdDDeoPq1u6f/lkGF9bFOkN/Yy2rz6F4YAKG4/DJP+6eJNaT\n07461rlW8YvaUVYx5uD56gnBOOC0JFqmCRJEdgzAxzCxoVctvyas6q5g2QKBgB3p\ng9dhxom9UxFEFnCShyRoXcR3W6O5NeCdYuySrbUXic0KKwo26KUl1eRwAbBOrA7v\nw+n864Aof+jcEuT6D/Rh/B6/T+CANHcE42i84ZhPehopsw8+H/lmk38IWXDfHT7G\nlRYJfQ/AAI7iM0ICFvf0U8iWALHKQ963yGmKBbbhAoGBALTA/DHcxzNummVrvZh2\n0jlWOBySt3q9Nq5umsT3Q27cfgubqnxJDr574xlw2rFay07FdinSDb4alQUz7k4E\nhe3IfqcBzUmEsw4fL0uC/KOj1kHbIQVEstZtFBS2e3mxlEm+xPQCNWB45enWmzyb\nlS3DYiIrrNZ95tjw4FYiwlw2\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "demo10/config/config.go",
    "content": "package config\n\nimport (\n\t\"strings\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n)\n\ntype Config struct {\n\tName string\n}\n\nfunc Init(cfg string) error {\n\tc := Config{\n\t\tName: cfg,\n\t}\n\n\t// 初始化配置文件\n\tif err := c.initConfig(); err != nil {\n\t\treturn err\n\t}\n\n\t// 初始化日志包\n\tc.initLog()\n\n\t// 监控配置文件变化并热加载程序\n\tc.watchConfig()\n\n\treturn nil\n}\n\nfunc (c *Config) initConfig() error {\n\tif c.Name != \"\" {\n\t\tviper.SetConfigFile(c.Name) // 如果指定了配置文件，则解析指定的配置文件\n\t} else {\n\t\tviper.AddConfigPath(\"conf\") // 如果没有指定配置文件，则解析默认的配置文件\n\t\tviper.SetConfigName(\"config\")\n\t}\n\tviper.SetConfigType(\"yaml\")     // 设置配置文件格式为YAML\n\tviper.AutomaticEnv()            // 读取匹配的环境变量\n\tviper.SetEnvPrefix(\"APISERVER\") // 读取环境变量的前缀为APISERVER\n\treplacer := strings.NewReplacer(\".\", \"_\")\n\tviper.SetEnvKeyReplacer(replacer)\n\tif err := viper.ReadInConfig(); err != nil { // viper解析配置文件\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *Config) initLog() {\n\tpassLagerCfg := log.PassLagerCfg{\n\t\tWriters:        viper.GetString(\"log.writers\"),\n\t\tLoggerLevel:    viper.GetString(\"log.logger_level\"),\n\t\tLoggerFile:     viper.GetString(\"log.logger_file\"),\n\t\tLogFormatText:  viper.GetBool(\"log.log_format_text\"),\n\t\tRollingPolicy:  viper.GetString(\"log.rollingPolicy\"),\n\t\tLogRotateDate:  viper.GetInt(\"log.log_rotate_date\"),\n\t\tLogRotateSize:  viper.GetInt(\"log.log_rotate_size\"),\n\t\tLogBackupCount: viper.GetInt(\"log.log_backup_count\"),\n\t}\n\n\tlog.InitWithConfig(&passLagerCfg)\n}\n\n// 监控配置文件变化并热加载程序\nfunc (c *Config) watchConfig() {\n\tviper.WatchConfig()\n\tviper.OnConfigChange(func(e fsnotify.Event) {\n\t\tlog.Infof(\"Config file changed: %s\", e.Name)\n\t})\n}\n"
  },
  {
    "path": "demo10/db.sql",
    "content": "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n/*!40101 SET NAMES utf8 */;\n/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;\n/*!40103 SET TIME_ZONE='+00:00' */;\n/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;\n/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n\nCREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */;\n\nUSE `db_apiserver`;\n\n--\n-- Table structure for table `tb_users`\n--\n\nDROP TABLE IF EXISTS `tb_users`;\n/*!40101 SET @saved_cs_client     = @@character_set_client */;\n/*!40101 SET character_set_client = utf8 */;\nCREATE TABLE `tb_users` (\n  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n  `username` varchar(255) NOT NULL,\n  `password` varchar(255) NOT NULL,\n  `createdAt` timestamp NULL DEFAULT NULL,\n  `updatedAt` timestamp NULL DEFAULT NULL,\n  `deletedAt` timestamp NULL DEFAULT NULL,\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `username` (`username`),\n  KEY `idx_tb_users_deletedAt` (`deletedAt`)\n) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;\n/*!40101 SET character_set_client = @saved_cs_client */;\n\n--\n-- Dumping data for table `tb_users`\n--\n\nLOCK TABLES `tb_users` WRITE;\n/*!40000 ALTER TABLE `tb_users` DISABLE KEYS */;\nINSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL);\n/*!40000 ALTER TABLE `tb_users` ENABLE KEYS */;\nUNLOCK TABLES;\n/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;\n\n/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;\n/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;\n/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n\n-- Dump completed on 2018-05-28  0:25:41\n"
  },
  {
    "path": "demo10/handler/handler.go",
    "content": "package handler\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype Response struct {\n\tCode    int         `json:\"code\"`\n\tMessage string      `json:\"message\"`\n\tData    interface{} `json:\"data\"`\n}\n\nfunc SendResponse(c *gin.Context, err error, data interface{}) {\n\tcode, message := errno.DecodeErr(err)\n\n\t// always return http.StatusOK\n\tc.JSON(http.StatusOK, Response{\n\t\tCode:    code,\n\t\tMessage: message,\n\t\tData:    data,\n\t})\n}\n"
  },
  {
    "path": "demo10/handler/sd/check.go",
    "content": "package sd\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/shirou/gopsutil/cpu\"\n\t\"github.com/shirou/gopsutil/disk\"\n\t\"github.com/shirou/gopsutil/load\"\n\t\"github.com/shirou/gopsutil/mem\"\n)\n\nconst (\n\tB  = 1\n\tKB = 1024 * B\n\tMB = 1024 * KB\n\tGB = 1024 * MB\n)\n\n// HealthCheck shows `OK` as the ping-pong result.\nfunc HealthCheck(c *gin.Context) {\n\tmessage := \"OK\"\n\tc.String(http.StatusOK, \"\\n\"+message)\n}\n\n// DiskCheck checks the disk usage.\nfunc DiskCheck(c *gin.Context) {\n\tu, _ := disk.Usage(\"/\")\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusOK\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n\n// CPUCheck checks the cpu usage.\nfunc CPUCheck(c *gin.Context) {\n\tcores, _ := cpu.Counts(false)\n\n\ta, _ := load.Avg()\n\tl1 := a.Load1\n\tl5 := a.Load5\n\tl15 := a.Load15\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif l5 >= float64(cores-1) {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if l5 >= float64(cores-2) {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Load average: %.2f, %.2f, %.2f | Cores: %d\", text, l1, l5, l15, cores)\n\tc.String(status, \"\\n\"+message)\n}\n\n// RAMCheck checks the disk usage.\nfunc RAMCheck(c *gin.Context) {\n\tu, _ := mem.VirtualMemory()\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n"
  },
  {
    "path": "demo10/handler/user/create.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// Create creates a new user account.\nfunc Create(c *gin.Context) {\n\tlog.Info(\"User Create function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\tvar r CreateRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tu := model.UserModel{\n\t\tUsername: r.Username,\n\t\tPassword: r.Password,\n\t}\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\t// Insert the user to the database.\n\tif err := u.Create(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\trsp := CreateResponse{\n\t\tUsername: r.Username,\n\t}\n\n\t// Show the user information.\n\tSendResponse(c, nil, rsp)\n}\n"
  },
  {
    "path": "demo10/handler/user/delete.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Delete delete an user by the user identifier.\nfunc Delete(c *gin.Context) {\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\tif err := model.DeleteUser(uint64(userId)); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo10/handler/user/get.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Get gets an user by the user identifier.\nfunc Get(c *gin.Context) {\n\tusername := c.Param(\"username\")\n\t// Get the user by the `username` from the database.\n\tuser, err := model.GetUser(username)\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrUserNotFound, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, user)\n}\n"
  },
  {
    "path": "demo10/handler/user/list.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/service\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// List list the users in the database.\nfunc List(c *gin.Context) {\n\tvar r ListRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tinfos, count, err := service.ListUser(r.Username, r.Offset, r.Limit)\n\tif err != nil {\n\t\tSendResponse(c, err, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, ListResponse{\n\t\tTotalCount: count,\n\t\tUserList:   infos,\n\t})\n}\n"
  },
  {
    "path": "demo10/handler/user/login.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/auth\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/pkg/token\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Login generates the authentication token\n// if the password was matched with the specified account.\nfunc Login(c *gin.Context) {\n\t// Binding the data with the user struct.\n\tvar u model.UserModel\n\tif err := c.Bind(&u); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\t// Get the user information by the login username.\n\td, err := model.GetUser(u.Username)\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrUserNotFound, nil)\n\t\treturn\n\t}\n\n\t// Compare the login password with the user password.\n\tif err := auth.Compare(d.Password, u.Password); err != nil {\n\t\tSendResponse(c, errno.ErrPasswordIncorrect, nil)\n\t\treturn\n\t}\n\n\t// Sign the json web token.\n\tt, err := token.Sign(c, token.Context{ID: d.Id, Username: d.Username}, \"\")\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrToken, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, model.Token{Token: t})\n}\n"
  },
  {
    "path": "demo10/handler/user/update.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// Update update a exist user account info.\nfunc Update(c *gin.Context) {\n\tlog.Info(\"Update function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\t// Get the user id from the url parameter.\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\n\t// Binding the user data.\n\tvar u model.UserModel\n\tif err := c.Bind(&u); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\t// We update the record based on the user id.\n\tu.Id = uint64(userId)\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\n\t// Save changed fields.\n\tif err := u.Update(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo10/handler/user/user.go",
    "content": "package user\n\nimport (\n\t\"apiserver/model\"\n)\n\ntype CreateRequest struct {\n\tUsername string `json:\"username\"`\n\tPassword string `json:\"password\"`\n}\n\ntype CreateResponse struct {\n\tUsername string `json:\"username\"`\n}\n\ntype ListRequest struct {\n\tUsername string `json:\"username\"`\n\tOffset   int    `json:\"offset\"`\n\tLimit    int    `json:\"limit\"`\n}\n\ntype ListResponse struct {\n\tTotalCount uint64            `json:\"totalCount\"`\n\tUserList   []*model.UserInfo `json:\"userList\"`\n}\n"
  },
  {
    "path": "demo10/main.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"apiserver/config\"\n\t\"apiserver/model\"\n\t\"apiserver/router\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tcfg = pflag.StringP(\"config\", \"c\", \"\", \"apiserver config file path.\")\n)\n\nfunc main() {\n\tpflag.Parse()\n\n\t// init config\n\tif err := config.Init(*cfg); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// init db\n\tmodel.DB.Init()\n\tdefer model.DB.Close()\n\n\t// Set gin mode.\n\tgin.SetMode(viper.GetString(\"runmode\"))\n\n\t// Create the Gin engine.\n\tg := gin.New()\n\n\t// Routes.\n\trouter.Load(\n\t\t// Cores.\n\t\tg,\n\n\t\t// Middlwares.\n\t\tmiddleware.Logging(),\n\t\tmiddleware.RequestId(),\n\t)\n\n\t// Ping the server to make sure the router is working.\n\tgo func() {\n\t\tif err := pingServer(); err != nil {\n\t\t\tlog.Fatal(\"The router has no response, or it might took too long to start up.\", err)\n\t\t}\n\t\tlog.Info(\"The router has been deployed successfully.\")\n\t}()\n\n\t// Start to listening the incoming requests.\n\tcert := viper.GetString(\"tls.cert\")\n\tkey := viper.GetString(\"tls.key\")\n\tif cert != \"\" && key != \"\" {\n\t\tgo func() {\n\t\t\tlog.Infof(\"Start to listening the incoming requests on https address: %s\", viper.GetString(\"tls.addr\"))\n\t\t\tlog.Info(http.ListenAndServeTLS(viper.GetString(\"tls.addr\"), cert, key, g).Error())\n\t\t}()\n\t}\n\n\tlog.Infof(\"Start to listening the incoming requests on http address: %s\", viper.GetString(\"addr\"))\n\tlog.Info(http.ListenAndServe(viper.GetString(\"addr\"), g).Error())\n}\n\n// pingServer pings the http server to make sure the router is working.\nfunc pingServer() error {\n\tfor i := 0; i < viper.GetInt(\"max_ping_count\"); i++ {\n\t\t// Ping the server by sending a GET request to `/health`.\n\t\tresp, err := http.Get(viper.GetString(\"url\") + \"/sd/health\")\n\t\tif err == nil && resp.StatusCode == 200 {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Sleep for a second to continue the next ping.\n\t\tlog.Info(\"Waiting for the router, retry in 1 second.\")\n\t\ttime.Sleep(time.Second)\n\t}\n\treturn errors.New(\"Cannot connect to the router.\")\n}\n"
  },
  {
    "path": "demo10/model/init.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n\t// MySQL driver.\n\t\"github.com/jinzhu/gorm\"\n\t_ \"github.com/jinzhu/gorm/dialects/mysql\"\n)\n\ntype Database struct {\n\tSelf   *gorm.DB\n\tDocker *gorm.DB\n}\n\nvar DB *Database\n\nfunc openDB(username, password, addr, name string) *gorm.DB {\n\tconfig := fmt.Sprintf(\"%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s\",\n\t\tusername,\n\t\tpassword,\n\t\taddr,\n\t\tname,\n\t\ttrue,\n\t\t//\"Asia/Shanghai\"),\n\t\t\"Local\")\n\n\tdb, err := gorm.Open(\"mysql\", config)\n\tif err != nil {\n\t\tlog.Errorf(err, \"Database connection failed. Database name: %s\", name)\n\t}\n\n\t// set for db connection\n\tsetupDB(db)\n\n\treturn db\n}\n\nfunc setupDB(db *gorm.DB) {\n\tdb.LogMode(viper.GetBool(\"gormlog\"))\n\t//db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数，默认值为0表示不限制.设置最大的连接数，可以避免并发太高导致连接mysql出现too many connections的错误。\n\tdb.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。\n}\n\n// used for cli\nfunc InitSelfDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"db.username\"),\n\t\tviper.GetString(\"db.password\"),\n\t\tviper.GetString(\"db.addr\"),\n\t\tviper.GetString(\"db.name\"))\n}\n\nfunc GetSelfDB() *gorm.DB {\n\treturn InitSelfDB()\n}\n\nfunc InitDockerDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"docker_db.username\"),\n\t\tviper.GetString(\"docker_db.password\"),\n\t\tviper.GetString(\"docker_db.addr\"),\n\t\tviper.GetString(\"docker_db.name\"))\n}\n\nfunc GetDockerDB() *gorm.DB {\n\treturn InitDockerDB()\n}\n\nfunc (db *Database) Init() {\n\tDB = &Database{\n\t\tSelf:   GetSelfDB(),\n\t\tDocker: GetDockerDB(),\n\t}\n}\n\nfunc (db *Database) Close() {\n\tDB.Self.Close()\n\tDB.Docker.Close()\n}\n"
  },
  {
    "path": "demo10/model/model.go",
    "content": "package model\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype BaseModel struct {\n\tId        uint64     `gorm:\"primary_key;AUTO_INCREMENT;column:id\" json:\"-\"`\n\tCreatedAt time.Time  `gorm:\"column:createdAt\" json:\"-\"`\n\tUpdatedAt time.Time  `gorm:\"column:updatedAt\" json:\"-\"`\n\tDeletedAt *time.Time `gorm:\"column:deletedAt\" sql:\"index\" json:\"-\"`\n}\n\ntype UserInfo struct {\n\tId        uint64 `json:\"id\"`\n\tUsername  string `json:\"username\"`\n\tSayHello  string `json:\"sayHello\"`\n\tPassword  string `json:\"password\"`\n\tCreatedAt string `json:\"createdAt\"`\n\tUpdatedAt string `json:\"updatedAt\"`\n}\n\ntype UserList struct {\n\tLock  *sync.Mutex\n\tIdMap map[uint64]*UserInfo\n}\n\n// Token represents a JSON web token.\ntype Token struct {\n\tToken string `json:\"token\"`\n}\n"
  },
  {
    "path": "demo10/model/user.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"apiserver/pkg/auth\"\n\t\"apiserver/pkg/constvar\"\n\n\tvalidator \"gopkg.in/go-playground/validator.v9\"\n)\n\n// User represents a registered user.\ntype UserModel struct {\n\tBaseModel\n\tUsername string `json:\"username\" gorm:\"column:username;not null\" binding:\"required\" validate:\"min=1,max=32\"`\n\tPassword string `json:\"password\" gorm:\"column:password;not null\" binding:\"required\" validate:\"min=5,max=128\"`\n}\n\nfunc (c *UserModel) TableName() string {\n\treturn \"tb_users\"\n}\n\n// Create creates a new user account.\nfunc (u *UserModel) Create() error {\n\treturn DB.Self.Create(&u).Error\n}\n\n// DeleteUser deletes the user by the user identifier.\nfunc DeleteUser(id uint64) error {\n\tuser := UserModel{}\n\tuser.BaseModel.Id = id\n\treturn DB.Self.Delete(&user).Error\n}\n\n// Update updates an user account information.\nfunc (u *UserModel) Update() error {\n\treturn DB.Self.Save(u).Error\n}\n\n// GetUser gets an user by the user identifier.\nfunc GetUser(username string) (*UserModel, error) {\n\tu := &UserModel{}\n\td := DB.Self.Where(\"username = ?\", username).First(&u)\n\treturn u, d.Error\n}\n\n// ListUser List all users\nfunc ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) {\n\tif limit == 0 {\n\t\tlimit = constvar.DefaultLimit\n\t}\n\n\tusers := make([]*UserModel, 0)\n\tvar count uint64\n\n\twhere := fmt.Sprintf(\"username like '%%%s%%'\", username)\n\tif err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\tif err := DB.Self.Where(where).Offset(offset).Limit(limit).Order(\"id desc\").Find(&users).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\treturn users, count, nil\n}\n\n// Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct).\nfunc (u *UserModel) Compare(pwd string) (err error) {\n\terr = auth.Compare(u.Password, pwd)\n\treturn\n}\n\n// Encrypt the user password.\nfunc (u *UserModel) Encrypt() (err error) {\n\tu.Password, err = auth.Encrypt(u.Password)\n\treturn\n}\n\n// Validate the fields.\nfunc (u *UserModel) Validate() error {\n\tvalidate := validator.New()\n\treturn validate.Struct(u)\n}\n"
  },
  {
    "path": "demo10/pkg/auth/auth.go",
    "content": "package auth\n\nimport \"golang.org/x/crypto/bcrypt\"\n\n// Encrypt encrypts the plain text with bcrypt.\nfunc Encrypt(source string) (string, error) {\n\thashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost)\n\treturn string(hashedBytes), err\n}\n\n// Compare compares the encrypted text with the plain text if it's the same.\nfunc Compare(hashedPassword, password string) error {\n\treturn bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))\n}\n"
  },
  {
    "path": "demo10/pkg/constvar/constvar.go",
    "content": "package constvar\n\nconst (\n\tDefaultLimit = 50\n)\n"
  },
  {
    "path": "demo10/pkg/errno/code.go",
    "content": "package errno\n\nvar (\n\t// Common errors\n\tOK                  = &Errno{Code: 0, Message: \"OK\"}\n\tInternalServerError = &Errno{Code: 10001, Message: \"Internal server error\"}\n\tErrBind             = &Errno{Code: 10002, Message: \"Error occurred while binding the request body to the struct.\"}\n\n\tErrValidation = &Errno{Code: 20001, Message: \"Validation failed.\"}\n\tErrDatabase   = &Errno{Code: 20002, Message: \"Database error.\"}\n\tErrToken      = &Errno{Code: 20003, Message: \"Error occurred while signing the JSON web token.\"}\n\n\t// user errors\n\tErrEncrypt           = &Errno{Code: 20101, Message: \"Error occurred while encrypting the user password.\"}\n\tErrUserNotFound      = &Errno{Code: 20102, Message: \"The user was not found.\"}\n\tErrTokenInvalid      = &Errno{Code: 20103, Message: \"The token was invalid.\"}\n\tErrPasswordIncorrect = &Errno{Code: 20104, Message: \"The password was incorrect.\"}\n)\n"
  },
  {
    "path": "demo10/pkg/errno/errno.go",
    "content": "package errno\n\nimport \"fmt\"\n\ntype Errno struct {\n\tCode    int\n\tMessage string\n}\n\nfunc (err Errno) Error() string {\n\treturn err.Message\n}\n\n// Err represents an error\ntype Err struct {\n\tCode    int\n\tMessage string\n\tErr     error\n}\n\nfunc New(errno *Errno, err error) *Err {\n\treturn &Err{Code: errno.Code, Message: errno.Message, Err: err}\n}\n\nfunc (err *Err) Add(message string) error {\n\terr.Message += \" \" + message\n\treturn err\n}\n\nfunc (err *Err) Addf(format string, args ...interface{}) error {\n\terr.Message += \" \" + fmt.Sprintf(format, args...)\n\treturn err\n}\n\nfunc (err *Err) Error() string {\n\treturn fmt.Sprintf(\"Err - code: %d, message: %s, error: %s\", err.Code, err.Message, err.Err)\n}\n\nfunc IsErrUserNotFound(err error) bool {\n\tcode, _ := DecodeErr(err)\n\treturn code == ErrUserNotFound.Code\n}\n\nfunc DecodeErr(err error) (int, string) {\n\tif err == nil {\n\t\treturn OK.Code, OK.Message\n\t}\n\n\tswitch typed := err.(type) {\n\tcase *Err:\n\t\treturn typed.Code, typed.Message\n\tcase *Errno:\n\t\treturn typed.Code, typed.Message\n\tdefault:\n\t}\n\n\treturn InternalServerError.Code, err.Error()\n}\n"
  },
  {
    "path": "demo10/pkg/token/token.go",
    "content": "package token\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\tjwt \"github.com/dgrijalva/jwt-go\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\t// ErrMissingHeader means the `Authorization` header was empty.\n\tErrMissingHeader = errors.New(\"The length of the `Authorization` header is zero.\")\n)\n\n// Context is the context of the JSON web token.\ntype Context struct {\n\tID       uint64\n\tUsername string\n}\n\n// secretFunc validates the secret format.\nfunc secretFunc(secret string) jwt.Keyfunc {\n\treturn func(token *jwt.Token) (interface{}, error) {\n\t\t// Make sure the `alg` is what we except.\n\t\tif _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {\n\t\t\treturn nil, jwt.ErrSignatureInvalid\n\t\t}\n\n\t\treturn []byte(secret), nil\n\t}\n}\n\n// Parse validates the token with the specified secret,\n// and returns the context if the token was valid.\nfunc Parse(tokenString string, secret string) (*Context, error) {\n\tctx := &Context{}\n\n\t// Parse the token.\n\ttoken, err := jwt.Parse(tokenString, secretFunc(secret))\n\n\t// Parse error.\n\tif err != nil {\n\t\treturn ctx, err\n\n\t\t// Read the token if it's valid.\n\t} else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {\n\t\tctx.ID = uint64(claims[\"id\"].(float64))\n\t\tctx.Username = claims[\"username\"].(string)\n\t\treturn ctx, nil\n\n\t\t// Other errors.\n\t} else {\n\t\treturn ctx, err\n\t}\n}\n\n// ParseRequest gets the token from the header and\n// pass it to the Parse function to parses the token.\nfunc ParseRequest(c *gin.Context) (*Context, error) {\n\theader := c.Request.Header.Get(\"Authorization\")\n\n\t// Load the jwt secret from config\n\tsecret := viper.GetString(\"jwt_secret\")\n\n\tif len(header) == 0 {\n\t\treturn &Context{}, ErrMissingHeader\n\t}\n\n\tvar t string\n\t// Parse the header to get the token part.\n\tfmt.Sscanf(header, \"Bearer %s\", &t)\n\treturn Parse(t, secret)\n}\n\n// Sign signs the context with the specified secret.\nfunc Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) {\n\t// Load the jwt secret from the Gin config if the secret isn't specified.\n\tif secret == \"\" {\n\t\tsecret = viper.GetString(\"jwt_secret\")\n\t}\n\t// The token content.\n\ttoken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{\n\t\t\"id\":       c.ID,\n\t\t\"username\": c.Username,\n\t\t\"nbf\":      time.Now().Unix(),\n\t\t\"iat\":      time.Now().Unix(),\n\t})\n\t// Sign the token with the specified secret.\n\ttokenString, err = token.SignedString([]byte(secret))\n\n\treturn\n}\n"
  },
  {
    "path": "demo10/router/middleware/auth.go",
    "content": "package middleware\n\nimport (\n\t\"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/pkg/token\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc AuthMiddleware() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// Parse the json web token.\n\t\tif _, err := token.ParseRequest(c); err != nil {\n\t\t\thandler.SendResponse(c, errno.ErrTokenInvalid, nil)\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "demo10/router/middleware/header.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// NoCache is a middleware function that appends headers\n// to prevent the client from caching the HTTP response.\nfunc NoCache(c *gin.Context) {\n\tc.Header(\"Cache-Control\", \"no-cache, no-store, max-age=0, must-revalidate, value\")\n\tc.Header(\"Expires\", \"Thu, 01 Jan 1970 00:00:00 GMT\")\n\tc.Header(\"Last-Modified\", time.Now().UTC().Format(http.TimeFormat))\n\tc.Next()\n}\n\n// Options is a middleware function that appends headers\n// for options requests and aborts then exits the middleware\n// chain and ends the request.\nfunc Options(c *gin.Context) {\n\tif c.Request.Method != \"OPTIONS\" {\n\t\tc.Next()\n\t} else {\n\t\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\t\tc.Header(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Access-Control-Allow-Headers\", \"authorization, origin, content-type, accept\")\n\t\tc.Header(\"Allow\", \"HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Content-Type\", \"application/json\")\n\t\tc.AbortWithStatus(200)\n\t}\n}\n\n// Secure is a middleware function that appends security\n// and resource access headers.\nfunc Secure(c *gin.Context) {\n\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\tc.Header(\"X-Frame-Options\", \"DENY\")\n\tc.Header(\"X-Content-Type-Options\", \"nosniff\")\n\tc.Header(\"X-XSS-Protection\", \"1; mode=block\")\n\tif c.Request.TLS != nil {\n\t\tc.Header(\"Strict-Transport-Security\", \"max-age=31536000\")\n\t}\n\n\t// Also consider adding Content-Security-Policy headers\n\t// c.Header(\"Content-Security-Policy\", \"script-src 'self' https://cdnjs.cloudflare.com\")\n}\n"
  },
  {
    "path": "demo10/router/middleware/logging.go",
    "content": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/willf/pad\"\n)\n\ntype bodyLogWriter struct {\n\tgin.ResponseWriter\n\tbody *bytes.Buffer\n}\n\nfunc (w bodyLogWriter) Write(b []byte) (int, error) {\n\tw.body.Write(b)\n\treturn w.ResponseWriter.Write(b)\n}\n\n// Logging is a middleware function that logs the each request.\nfunc Logging() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tstart := time.Now().UTC()\n\t\tpath := c.Request.URL.Path\n\n\t\treg := regexp.MustCompile(\"(/v1/user|/login)\")\n\t\tif !reg.MatchString(path) {\n\t\t\treturn\n\t\t}\n\n\t\t// Skip for the health check requests.\n\t\tif path == \"/sd/health\" || path == \"/sd/ram\" || path == \"/sd/cpu\" || path == \"/sd/disk\" {\n\t\t\treturn\n\t\t}\n\n\t\t// Read the Body content\n\t\tvar bodyBytes []byte\n\t\tif c.Request.Body != nil {\n\t\t\tbodyBytes, _ = ioutil.ReadAll(c.Request.Body)\n\t\t}\n\n\t\t// Restore the io.ReadCloser to its original state\n\t\tc.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))\n\n\t\t// The basic informations.\n\t\tmethod := c.Request.Method\n\t\tip := c.ClientIP()\n\n\t\t//log.Debugf(\"New request come in, path: %s, Method: %s, body `%s`\", path, method, string(bodyBytes))\n\t\tblw := &bodyLogWriter{\n\t\t\tbody:           bytes.NewBufferString(\"\"),\n\t\t\tResponseWriter: c.Writer,\n\t\t}\n\t\tc.Writer = blw\n\n\t\t// Continue.\n\t\tc.Next()\n\n\t\t// Calculates the latency.\n\t\tend := time.Now().UTC()\n\t\tlatency := end.Sub(start)\n\n\t\tcode, message := -1, \"\"\n\n\t\t// get code and message\n\t\tvar response handler.Response\n\t\tif err := json.Unmarshal(blw.body.Bytes(), &response); err != nil {\n\t\t\tlog.Errorf(err, \"response body can not unmarshal to model.Response struct, body: `%s`\", blw.body.Bytes())\n\t\t\tcode = errno.InternalServerError.Code\n\t\t\tmessage = err.Error()\n\t\t} else {\n\t\t\tcode = response.Code\n\t\t\tmessage = response.Message\n\t\t}\n\n\t\tlog.Infof(\"%-13s | %-12s | %s %s | {code: %d, message: %s}\", latency, ip, pad.Right(method, 5, \"\"), path, code, message)\n\t}\n}\n"
  },
  {
    "path": "demo10/router/middleware/requestid.go",
    "content": "package middleware\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/satori/go.uuid\"\n)\n\nfunc RequestId() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// Check for incoming header, use it if exists\n\t\trequestId := c.Request.Header.Get(\"X-Request-Id\")\n\n\t\t// Create request id with UUID4\n\t\tif requestId == \"\" {\n\t\t\tu4, _ := uuid.NewV4()\n\t\t\trequestId = u4.String()\n\t\t}\n\n\t\t// Expose it for use in the application\n\t\tc.Set(\"X-Request-Id\", requestId)\n\n\t\t// Set X-Request-Id header\n\t\tc.Writer.Header().Set(\"X-Request-Id\", requestId)\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "demo10/router/router.go",
    "content": "package router\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/handler/sd\"\n\t\"apiserver/handler/user\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Load loads the middlewares, routes, handlers.\nfunc Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {\n\t// Middlewares.\n\tg.Use(gin.Recovery())\n\tg.Use(middleware.NoCache)\n\tg.Use(middleware.Options)\n\tg.Use(middleware.Secure)\n\tg.Use(mw...)\n\t// 404 Handler.\n\tg.NoRoute(func(c *gin.Context) {\n\t\tc.String(http.StatusNotFound, \"The incorrect API route.\")\n\t})\n\n\t// api for authentication functionalities\n\tg.POST(\"/login\", user.Login)\n\n\t// The user handlers, requiring authentication\n\tu := g.Group(\"/v1/user\")\n\tu.Use(middleware.AuthMiddleware())\n\t{\n\t\tu.POST(\"\", user.Create)\n\t\tu.DELETE(\"/:id\", user.Delete)\n\t\tu.PUT(\"/:id\", user.Update)\n\t\tu.GET(\"\", user.List)\n\t\tu.GET(\"/:username\", user.Get)\n\t}\n\n\t// The health check handlers\n\tsvcd := g.Group(\"/sd\")\n\t{\n\t\tsvcd.GET(\"/health\", sd.HealthCheck)\n\t\tsvcd.GET(\"/disk\", sd.DiskCheck)\n\t\tsvcd.GET(\"/cpu\", sd.CPUCheck)\n\t\tsvcd.GET(\"/ram\", sd.RAMCheck)\n\t}\n\n\treturn g\n}\n"
  },
  {
    "path": "demo10/service/service.go",
    "content": "package service\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\n\t\"apiserver/model\"\n\t\"apiserver/util\"\n)\n\nfunc ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) {\n\tinfos := make([]*model.UserInfo, 0)\n\tusers, count, err := model.ListUser(username, offset, limit)\n\tif err != nil {\n\t\treturn nil, count, err\n\t}\n\n\tids := []uint64{}\n\tfor _, user := range users {\n\t\tids = append(ids, user.Id)\n\t}\n\n\twg := sync.WaitGroup{}\n\tuserList := model.UserList{\n\t\tLock:  new(sync.Mutex),\n\t\tIdMap: make(map[uint64]*model.UserInfo, len(users)),\n\t}\n\n\terrChan := make(chan error, 1)\n\tfinished := make(chan bool, 1)\n\n\t// Improve query efficiency in parallel\n\tfor _, u := range users {\n\t\twg.Add(1)\n\t\tgo func(u *model.UserModel) {\n\t\t\tdefer wg.Done()\n\n\t\t\tshortId, err := util.GenShortId()\n\t\t\tif err != nil {\n\t\t\t\terrChan <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tuserList.Lock.Lock()\n\t\t\tdefer userList.Lock.Unlock()\n\t\t\tuserList.IdMap[u.Id] = &model.UserInfo{\n\t\t\t\tId:        u.Id,\n\t\t\t\tUsername:  u.Username,\n\t\t\t\tSayHello:  fmt.Sprintf(\"Hello %s\", shortId),\n\t\t\t\tPassword:  u.Password,\n\t\t\t\tCreatedAt: u.CreatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t\tUpdatedAt: u.UpdatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t}\n\t\t}(u)\n\t}\n\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(finished)\n\t}()\n\n\tselect {\n\tcase <-finished:\n\tcase err := <-errChan:\n\t\treturn nil, count, err\n\t}\n\n\tfor _, id := range ids {\n\t\tinfos = append(infos, userList.IdMap[id])\n\t}\n\n\treturn infos, count, nil\n}\n"
  },
  {
    "path": "demo10/util/util.go",
    "content": "package util\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/teris-io/shortid\"\n)\n\nfunc GenShortId() (string, error) {\n\treturn shortid.Generate()\n}\n\nfunc GetReqID(c *gin.Context) string {\n\tv, ok := c.Get(\"X-Request-Id\")\n\tif !ok {\n\t\treturn \"\"\n\t}\n\tif requestId, ok := v.(string); ok {\n\t\treturn requestId\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "demo11/Makefile",
    "content": "all: gotool\n\t@go build -v .\nclean:\n\trm -f apiserver\n\tfind . -name \"[._]*.s[a-w][a-z]\" | xargs -i rm -f {}\ngotool:\n\tgofmt -w .\n\tgo tool vet . |& grep -v vendor;true\nca:\n\topenssl 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\"\n\nhelp:\n\t@echo \"make - compile the source code\"\n\t@echo \"make clean - remove binary file and vim swp files\"\n\t@echo \"make gotool - run go tool 'fmt' and 'vet'\"\n\t@echo \"make ca - generate ca files\"\n\n.PHONY: clean gotool ca help\n\n\n"
  },
  {
    "path": "demo11/README.md",
    "content": "进阶：用Makefile管理API项目\n"
  },
  {
    "path": "demo11/conf/config.yaml",
    "content": "runmode: debug                 # 开发模式, debug, release, test\naddr: :8080                  # HTTP绑定端口\nname: apiserver              # API Server的名字\nurl: http://127.0.0.1:8080   # pingServer函数请求的API服务器的ip:port\nmax_ping_count: 10           # pingServer函数try的次数\njwt_secret: Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5\ntls:\n  addr: :8081\n  cert: conf/server.crt\n  key: conf/server.key\nlog:\n  writers: stdout\n  logger_level: DEBUG\n  logger_file: log/apiserver.log\n  log_format_text: true\n  rollingPolicy: size\n  log_rotate_date: 1\n  log_rotate_size: 1\n  log_backup_count: 7\ndb:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\ndocker_db:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\n"
  },
  {
    "path": "demo11/conf/server.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIID2TCCAsGgAwIBAgIJAI/8axTNy37vMA0GCSqGSIb3DQEBBQUAMIGCMQswCQYD\nVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO\nUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAkxMjcuMC4wLjEx\nGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNvbTAeFw0xODA2MDQwNjMwMzFaFw0y\nODA2MDEwNjMwMzFaMIGCMQswCQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYD\nVQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklU\nMRIwEAYDVQQDDAkxMjcuMC4wLjExGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNv\nbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKHbnpmaxHokFC8rnJlG\n42eL4C3tog7rffv96DrwDajdFNk8DeaNUpZ+R4X4YPecumsCsbol2dDC1IHHGJUN\nq/A1vRjyDptMLmzp30p1aqVPcz+j8GMXtZUflLgup+HeKZRdrsk/0SaH1C3ywUBm\nyDMNHUxEvoFj0x9aJoPutnkEk9miZt/kF2vxlTbKFM14/TecVr3ljNwTebJAqEw9\niaZpNXn/IQV2I2PTGlxjyT6XNsCchjMTJ9y/XS9rBeX6BFHgQsV52CW1mWz8FieM\n/nyF9hE/TZKCpMtHWEYIdBbrxxY0BlhMxEtyIFzTha5zltFrQJGUYz7RIlyuabjk\nor8CAwEAAaNQME4wHQYDVR0OBBYEFIjLBQlToqUDoyOLeUd6HeaqiiLJMB8GA1Ud\nIwQYMBaAFIjLBQlToqUDoyOLeUd6HeaqiiLJMAwGA1UdEwQFMAMBAf8wDQYJKoZI\nhvcNAQEFBQADggEBAHAqpqGNUNgYCr3JvrVULDqFKmqLpvDKfLd6kmtnFzvuZLSz\nANE2RXXqQ5Ms3F0oHqZQq4aOvgvfas5dBLoToPL/ip+GibQ7O/1PsPD5bZaczUel\nAnmClX4ewcOEWmZx9mbYOz2C8RREvKU3MoBERrWA33LquYH0+8W8WHyMKNNF97oD\nBgWZ8R1t3N59ySZyHcye7ddlJUubRbdyCGrHbmziHV3Cj61NJiwS2JRvhYkc71M6\n8921j63qEM6i0NusFYG/w0GM3x/OeaeY/om6x+IOhJp3vIa1JPSekVyNSCwDEc3B\n+8WmVFaeMMXwErSkuu8nCbqRP8J1jiKgzpG/92g=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "demo11/conf/server.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCh256ZmsR6JBQv\nK5yZRuNni+At7aIO6337/eg68A2o3RTZPA3mjVKWfkeF+GD3nLprArG6JdnQwtSB\nxxiVDavwNb0Y8g6bTC5s6d9KdWqlT3M/o/BjF7WVH5S4Lqfh3imUXa7JP9Emh9Qt\n8sFAZsgzDR1MRL6BY9MfWiaD7rZ5BJPZombf5Bdr8ZU2yhTNeP03nFa95YzcE3my\nQKhMPYmmaTV5/yEFdiNj0xpcY8k+lzbAnIYzEyfcv10vawXl+gRR4ELFedgltZls\n/BYnjP58hfYRP02SgqTLR1hGCHQW68cWNAZYTMRLciBc04Wuc5bRa0CRlGM+0SJc\nrmm45KK/AgMBAAECggEASNMWvgf7pPT8u+iEchaKFLnDqQaFZu8f5TRtu67shnDK\ng59YpcYqRZoVtjp17pLu8Vzp+FY1dY9jq+yXq+DV3qNfLI0kc01IiiqEE+1WiYCA\n2z541y0Av1LRSDl9wcuCq8Wm8der1AlDN1VFDCPyqb2Z1AoOKQtwH2ghcjUCltn4\nNxuMYOVdql+Idl/7PCEha9pW3DQvPqJVXRPvBh6tB7FFuUOIq4+EFRUWWZlyCJfQ\nfYxB9jcFUWmAUieV3iBajg6H6QVkj/STLSgKuSktEBM2mqyL4slHXFWRt7+NQikg\ntOEWnqcTC4Ei2Dyam4SBXHJ1wT8AJV4kMTrN1ZBRmQKBgQDOMcIGHyuBI1aHcvPW\nTkein7bslFAKsA1LBqnw+alWzSHMKEmfWbBjeW0FQTIGEYMXwIq5JhnNcS5Kb5Qt\nBZgbBPDb7S80DtJnkno3iJ6hpijE3X/29lezigIuGibO5MD0Na1am2BP0iU77ifV\nxpYvj/j/pkdcTLM7hRDImSeNIwKBgQDI9EUFkGi6H9NZEles+MCHenVRYUBT2PXe\nhmtd9d5f4v3CmRsXvl4Dl/slIrJ9kuZktAck3Ocx6D0hnZcj1mwq077K7H3QqKCl\nzRwB11H5v/imsZ1ey0hLVPhCzr2miRPM/9oL5B2st9eotzhyRcrMn+oYZSSt79S8\nQ3YDiZ7TtQKBgENSr7z79GJnvVrgR4kTagRJDZrVGgVDUjPK6zXI7mdu9rgH93HW\nAOeZv+TVUpX0pc7diO3G6OnRKIIZSFIi33UC+fl0ydK/fCdhBhKXwuOYsvsEL0Hd\nUOlICEoxM7adrfqOhBlvXdTyEkItEkiUXHkPEwe1rNsQF/05By/YAbftAoGAYB2F\njd2+WZezTN0bFl58J9CIoH31eKVDJEYCwJRC4nX9jcARV0/0Q5/DvcVUvf8vN2ds\nK1OFOTetVZC8o6WBYxKYJRLsMosVG3h5NuA4E06grYoyjQ6J644emEWuLCNQVzLg\npeNb1iqweb/4vZ9oGms6WqS14IPfqpRRs+t1DikCgYEAyFtQKlLmNm/EirOMqjS2\n4/zMRj4B6n8BB8ZJsVC/MkuD+4B03rY44pP324cJnqVIcug9Zoijffnh1qbtZEc/\nMhNCa4a2MY0qcd4r/43cGbjqv94En7e5TgkH/79W+lbR3uBayt33RuuKStauZkf/\n2eBi4LlGO5stoeD63h9Ten0=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "demo11/config/config.go",
    "content": "package config\n\nimport (\n\t\"strings\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n)\n\ntype Config struct {\n\tName string\n}\n\nfunc Init(cfg string) error {\n\tc := Config{\n\t\tName: cfg,\n\t}\n\n\t// 初始化配置文件\n\tif err := c.initConfig(); err != nil {\n\t\treturn err\n\t}\n\n\t// 初始化日志包\n\tc.initLog()\n\n\t// 监控配置文件变化并热加载程序\n\tc.watchConfig()\n\n\treturn nil\n}\n\nfunc (c *Config) initConfig() error {\n\tif c.Name != \"\" {\n\t\tviper.SetConfigFile(c.Name) // 如果指定了配置文件，则解析指定的配置文件\n\t} else {\n\t\tviper.AddConfigPath(\"conf\") // 如果没有指定配置文件，则解析默认的配置文件\n\t\tviper.SetConfigName(\"config\")\n\t}\n\tviper.SetConfigType(\"yaml\")     // 设置配置文件格式为YAML\n\tviper.AutomaticEnv()            // 读取匹配的环境变量\n\tviper.SetEnvPrefix(\"APISERVER\") // 读取环境变量的前缀为APISERVER\n\treplacer := strings.NewReplacer(\".\", \"_\")\n\tviper.SetEnvKeyReplacer(replacer)\n\tif err := viper.ReadInConfig(); err != nil { // viper解析配置文件\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *Config) initLog() {\n\tpassLagerCfg := log.PassLagerCfg{\n\t\tWriters:        viper.GetString(\"log.writers\"),\n\t\tLoggerLevel:    viper.GetString(\"log.logger_level\"),\n\t\tLoggerFile:     viper.GetString(\"log.logger_file\"),\n\t\tLogFormatText:  viper.GetBool(\"log.log_format_text\"),\n\t\tRollingPolicy:  viper.GetString(\"log.rollingPolicy\"),\n\t\tLogRotateDate:  viper.GetInt(\"log.log_rotate_date\"),\n\t\tLogRotateSize:  viper.GetInt(\"log.log_rotate_size\"),\n\t\tLogBackupCount: viper.GetInt(\"log.log_backup_count\"),\n\t}\n\n\tlog.InitWithConfig(&passLagerCfg)\n}\n\n// 监控配置文件变化并热加载程序\nfunc (c *Config) watchConfig() {\n\tviper.WatchConfig()\n\tviper.OnConfigChange(func(e fsnotify.Event) {\n\t\tlog.Infof(\"Config file changed: %s\", e.Name)\n\t})\n}\n"
  },
  {
    "path": "demo11/db.sql",
    "content": "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n/*!40101 SET NAMES utf8 */;\n/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;\n/*!40103 SET TIME_ZONE='+00:00' */;\n/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;\n/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n\nCREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */;\n\nUSE `db_apiserver`;\n\n--\n-- Table structure for table `tb_users`\n--\n\nDROP TABLE IF EXISTS `tb_users`;\n/*!40101 SET @saved_cs_client     = @@character_set_client */;\n/*!40101 SET character_set_client = utf8 */;\nCREATE TABLE `tb_users` (\n  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n  `username` varchar(255) NOT NULL,\n  `password` varchar(255) NOT NULL,\n  `createdAt` timestamp NULL DEFAULT NULL,\n  `updatedAt` timestamp NULL DEFAULT NULL,\n  `deletedAt` timestamp NULL DEFAULT NULL,\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `username` (`username`),\n  KEY `idx_tb_users_deletedAt` (`deletedAt`)\n) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;\n/*!40101 SET character_set_client = @saved_cs_client */;\n\n--\n-- Dumping data for table `tb_users`\n--\n\nLOCK TABLES `tb_users` WRITE;\n/*!40000 ALTER TABLE `tb_users` DISABLE KEYS */;\nINSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL);\n/*!40000 ALTER TABLE `tb_users` ENABLE KEYS */;\nUNLOCK TABLES;\n/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;\n\n/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;\n/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;\n/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n\n-- Dump completed on 2018-05-28  0:25:41\n"
  },
  {
    "path": "demo11/handler/handler.go",
    "content": "package handler\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype Response struct {\n\tCode    int         `json:\"code\"`\n\tMessage string      `json:\"message\"`\n\tData    interface{} `json:\"data\"`\n}\n\nfunc SendResponse(c *gin.Context, err error, data interface{}) {\n\tcode, message := errno.DecodeErr(err)\n\n\t// always return http.StatusOK\n\tc.JSON(http.StatusOK, Response{\n\t\tCode:    code,\n\t\tMessage: message,\n\t\tData:    data,\n\t})\n}\n"
  },
  {
    "path": "demo11/handler/sd/check.go",
    "content": "package sd\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/shirou/gopsutil/cpu\"\n\t\"github.com/shirou/gopsutil/disk\"\n\t\"github.com/shirou/gopsutil/load\"\n\t\"github.com/shirou/gopsutil/mem\"\n)\n\nconst (\n\tB  = 1\n\tKB = 1024 * B\n\tMB = 1024 * KB\n\tGB = 1024 * MB\n)\n\n// HealthCheck shows `OK` as the ping-pong result.\nfunc HealthCheck(c *gin.Context) {\n\tmessage := \"OK\"\n\tc.String(http.StatusOK, \"\\n\"+message)\n}\n\n// DiskCheck checks the disk usage.\nfunc DiskCheck(c *gin.Context) {\n\tu, _ := disk.Usage(\"/\")\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusOK\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n\n// CPUCheck checks the cpu usage.\nfunc CPUCheck(c *gin.Context) {\n\tcores, _ := cpu.Counts(false)\n\n\ta, _ := load.Avg()\n\tl1 := a.Load1\n\tl5 := a.Load5\n\tl15 := a.Load15\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif l5 >= float64(cores-1) {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if l5 >= float64(cores-2) {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Load average: %.2f, %.2f, %.2f | Cores: %d\", text, l1, l5, l15, cores)\n\tc.String(status, \"\\n\"+message)\n}\n\n// RAMCheck checks the disk usage.\nfunc RAMCheck(c *gin.Context) {\n\tu, _ := mem.VirtualMemory()\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n"
  },
  {
    "path": "demo11/handler/user/create.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// Create creates a new user account.\nfunc Create(c *gin.Context) {\n\tlog.Info(\"User Create function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\tvar r CreateRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tu := model.UserModel{\n\t\tUsername: r.Username,\n\t\tPassword: r.Password,\n\t}\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\t// Insert the user to the database.\n\tif err := u.Create(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\trsp := CreateResponse{\n\t\tUsername: r.Username,\n\t}\n\n\t// Show the user information.\n\tSendResponse(c, nil, rsp)\n}\n"
  },
  {
    "path": "demo11/handler/user/delete.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Delete delete an user by the user identifier.\nfunc Delete(c *gin.Context) {\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\tif err := model.DeleteUser(uint64(userId)); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo11/handler/user/get.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Get gets an user by the user identifier.\nfunc Get(c *gin.Context) {\n\tusername := c.Param(\"username\")\n\t// Get the user by the `username` from the database.\n\tuser, err := model.GetUser(username)\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrUserNotFound, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, user)\n}\n"
  },
  {
    "path": "demo11/handler/user/list.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/service\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// List list the users in the database.\nfunc List(c *gin.Context) {\n\tvar r ListRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tinfos, count, err := service.ListUser(r.Username, r.Offset, r.Limit)\n\tif err != nil {\n\t\tSendResponse(c, err, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, ListResponse{\n\t\tTotalCount: count,\n\t\tUserList:   infos,\n\t})\n}\n"
  },
  {
    "path": "demo11/handler/user/login.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/auth\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/pkg/token\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Login generates the authentication token\n// if the password was matched with the specified account.\nfunc Login(c *gin.Context) {\n\t// Binding the data with the user struct.\n\tvar u model.UserModel\n\tif err := c.Bind(&u); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\t// Get the user information by the login username.\n\td, err := model.GetUser(u.Username)\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrUserNotFound, nil)\n\t\treturn\n\t}\n\n\t// Compare the login password with the user password.\n\tif err := auth.Compare(d.Password, u.Password); err != nil {\n\t\tSendResponse(c, errno.ErrPasswordIncorrect, nil)\n\t\treturn\n\t}\n\n\t// Sign the json web token.\n\tt, err := token.Sign(c, token.Context{ID: d.Id, Username: d.Username}, \"\")\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrToken, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, model.Token{Token: t})\n}\n"
  },
  {
    "path": "demo11/handler/user/update.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// Update update a exist user account info.\nfunc Update(c *gin.Context) {\n\tlog.Info(\"Update function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\t// Get the user id from the url parameter.\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\n\t// Binding the user data.\n\tvar u model.UserModel\n\tif err := c.Bind(&u); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\t// We update the record based on the user id.\n\tu.Id = uint64(userId)\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\n\t// Save changed fields.\n\tif err := u.Update(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo11/handler/user/user.go",
    "content": "package user\n\nimport (\n\t\"apiserver/model\"\n)\n\ntype CreateRequest struct {\n\tUsername string `json:\"username\"`\n\tPassword string `json:\"password\"`\n}\n\ntype CreateResponse struct {\n\tUsername string `json:\"username\"`\n}\n\ntype ListRequest struct {\n\tUsername string `json:\"username\"`\n\tOffset   int    `json:\"offset\"`\n\tLimit    int    `json:\"limit\"`\n}\n\ntype ListResponse struct {\n\tTotalCount uint64            `json:\"totalCount\"`\n\tUserList   []*model.UserInfo `json:\"userList\"`\n}\n"
  },
  {
    "path": "demo11/main.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"apiserver/config\"\n\t\"apiserver/model\"\n\t\"apiserver/router\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tcfg = pflag.StringP(\"config\", \"c\", \"\", \"apiserver config file path.\")\n)\n\nfunc main() {\n\tpflag.Parse()\n\n\t// init config\n\tif err := config.Init(*cfg); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// init db\n\tmodel.DB.Init()\n\tdefer model.DB.Close()\n\n\t// Set gin mode.\n\tgin.SetMode(viper.GetString(\"runmode\"))\n\n\t// Create the Gin engine.\n\tg := gin.New()\n\n\t// Routes.\n\trouter.Load(\n\t\t// Cores.\n\t\tg,\n\n\t\t// Middlwares.\n\t\tmiddleware.Logging(),\n\t\tmiddleware.RequestId(),\n\t)\n\n\t// Ping the server to make sure the router is working.\n\tgo func() {\n\t\tif err := pingServer(); err != nil {\n\t\t\tlog.Fatal(\"The router has no response, or it might took too long to start up.\", err)\n\t\t}\n\t\tlog.Info(\"The router has been deployed successfully.\")\n\t}()\n\n\t// Start to listening the incoming requests.\n\tcert := viper.GetString(\"tls.cert\")\n\tkey := viper.GetString(\"tls.key\")\n\tif cert != \"\" && key != \"\" {\n\t\tgo func() {\n\t\t\tlog.Infof(\"Start to listening the incoming requests on https address: %s\", viper.GetString(\"tls.addr\"))\n\t\t\tlog.Info(http.ListenAndServeTLS(viper.GetString(\"tls.addr\"), cert, key, g).Error())\n\t\t}()\n\t}\n\n\tlog.Infof(\"Start to listening the incoming requests on http address: %s\", viper.GetString(\"addr\"))\n\tlog.Info(http.ListenAndServe(viper.GetString(\"addr\"), g).Error())\n}\n\n// pingServer pings the http server to make sure the router is working.\nfunc pingServer() error {\n\tfor i := 0; i < viper.GetInt(\"max_ping_count\"); i++ {\n\t\t// Ping the server by sending a GET request to `/health`.\n\t\tresp, err := http.Get(viper.GetString(\"url\") + \"/sd/health\")\n\t\tif err == nil && resp.StatusCode == 200 {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Sleep for a second to continue the next ping.\n\t\tlog.Info(\"Waiting for the router, retry in 1 second.\")\n\t\ttime.Sleep(time.Second)\n\t}\n\treturn errors.New(\"Cannot connect to the router.\")\n}\n"
  },
  {
    "path": "demo11/model/init.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n\t// MySQL driver.\n\t\"github.com/jinzhu/gorm\"\n\t_ \"github.com/jinzhu/gorm/dialects/mysql\"\n)\n\ntype Database struct {\n\tSelf   *gorm.DB\n\tDocker *gorm.DB\n}\n\nvar DB *Database\n\nfunc openDB(username, password, addr, name string) *gorm.DB {\n\tconfig := fmt.Sprintf(\"%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s\",\n\t\tusername,\n\t\tpassword,\n\t\taddr,\n\t\tname,\n\t\ttrue,\n\t\t//\"Asia/Shanghai\"),\n\t\t\"Local\")\n\n\tdb, err := gorm.Open(\"mysql\", config)\n\tif err != nil {\n\t\tlog.Errorf(err, \"Database connection failed. Database name: %s\", name)\n\t}\n\n\t// set for db connection\n\tsetupDB(db)\n\n\treturn db\n}\n\nfunc setupDB(db *gorm.DB) {\n\tdb.LogMode(viper.GetBool(\"gormlog\"))\n\t//db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数，默认值为0表示不限制.设置最大的连接数，可以避免并发太高导致连接mysql出现too many connections的错误。\n\tdb.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。\n}\n\n// used for cli\nfunc InitSelfDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"db.username\"),\n\t\tviper.GetString(\"db.password\"),\n\t\tviper.GetString(\"db.addr\"),\n\t\tviper.GetString(\"db.name\"))\n}\n\nfunc GetSelfDB() *gorm.DB {\n\treturn InitSelfDB()\n}\n\nfunc InitDockerDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"docker_db.username\"),\n\t\tviper.GetString(\"docker_db.password\"),\n\t\tviper.GetString(\"docker_db.addr\"),\n\t\tviper.GetString(\"docker_db.name\"))\n}\n\nfunc GetDockerDB() *gorm.DB {\n\treturn InitDockerDB()\n}\n\nfunc (db *Database) Init() {\n\tDB = &Database{\n\t\tSelf:   GetSelfDB(),\n\t\tDocker: GetDockerDB(),\n\t}\n}\n\nfunc (db *Database) Close() {\n\tDB.Self.Close()\n\tDB.Docker.Close()\n}\n"
  },
  {
    "path": "demo11/model/model.go",
    "content": "package model\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype BaseModel struct {\n\tId        uint64     `gorm:\"primary_key;AUTO_INCREMENT;column:id\" json:\"-\"`\n\tCreatedAt time.Time  `gorm:\"column:createdAt\" json:\"-\"`\n\tUpdatedAt time.Time  `gorm:\"column:updatedAt\" json:\"-\"`\n\tDeletedAt *time.Time `gorm:\"column:deletedAt\" sql:\"index\" json:\"-\"`\n}\n\ntype UserInfo struct {\n\tId        uint64 `json:\"id\"`\n\tUsername  string `json:\"username\"`\n\tSayHello  string `json:\"sayHello\"`\n\tPassword  string `json:\"password\"`\n\tCreatedAt string `json:\"createdAt\"`\n\tUpdatedAt string `json:\"updatedAt\"`\n}\n\ntype UserList struct {\n\tLock  *sync.Mutex\n\tIdMap map[uint64]*UserInfo\n}\n\n// Token represents a JSON web token.\ntype Token struct {\n\tToken string `json:\"token\"`\n}\n"
  },
  {
    "path": "demo11/model/user.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"apiserver/pkg/auth\"\n\t\"apiserver/pkg/constvar\"\n\n\tvalidator \"gopkg.in/go-playground/validator.v9\"\n)\n\n// User represents a registered user.\ntype UserModel struct {\n\tBaseModel\n\tUsername string `json:\"username\" gorm:\"column:username;not null\" binding:\"required\" validate:\"min=1,max=32\"`\n\tPassword string `json:\"password\" gorm:\"column:password;not null\" binding:\"required\" validate:\"min=5,max=128\"`\n}\n\nfunc (c *UserModel) TableName() string {\n\treturn \"tb_users\"\n}\n\n// Create creates a new user account.\nfunc (u *UserModel) Create() error {\n\treturn DB.Self.Create(&u).Error\n}\n\n// DeleteUser deletes the user by the user identifier.\nfunc DeleteUser(id uint64) error {\n\tuser := UserModel{}\n\tuser.BaseModel.Id = id\n\treturn DB.Self.Delete(&user).Error\n}\n\n// Update updates an user account information.\nfunc (u *UserModel) Update() error {\n\treturn DB.Self.Save(u).Error\n}\n\n// GetUser gets an user by the user identifier.\nfunc GetUser(username string) (*UserModel, error) {\n\tu := &UserModel{}\n\td := DB.Self.Where(\"username = ?\", username).First(&u)\n\treturn u, d.Error\n}\n\n// ListUser List all users\nfunc ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) {\n\tif limit == 0 {\n\t\tlimit = constvar.DefaultLimit\n\t}\n\n\tusers := make([]*UserModel, 0)\n\tvar count uint64\n\n\twhere := fmt.Sprintf(\"username like '%%%s%%'\", username)\n\tif err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\tif err := DB.Self.Where(where).Offset(offset).Limit(limit).Order(\"id desc\").Find(&users).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\treturn users, count, nil\n}\n\n// Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct).\nfunc (u *UserModel) Compare(pwd string) (err error) {\n\terr = auth.Compare(u.Password, pwd)\n\treturn\n}\n\n// Encrypt the user password.\nfunc (u *UserModel) Encrypt() (err error) {\n\tu.Password, err = auth.Encrypt(u.Password)\n\treturn\n}\n\n// Validate the fields.\nfunc (u *UserModel) Validate() error {\n\tvalidate := validator.New()\n\treturn validate.Struct(u)\n}\n"
  },
  {
    "path": "demo11/pkg/auth/auth.go",
    "content": "package auth\n\nimport \"golang.org/x/crypto/bcrypt\"\n\n// Encrypt encrypts the plain text with bcrypt.\nfunc Encrypt(source string) (string, error) {\n\thashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost)\n\treturn string(hashedBytes), err\n}\n\n// Compare compares the encrypted text with the plain text if it's the same.\nfunc Compare(hashedPassword, password string) error {\n\treturn bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))\n}\n"
  },
  {
    "path": "demo11/pkg/constvar/constvar.go",
    "content": "package constvar\n\nconst (\n\tDefaultLimit = 50\n)\n"
  },
  {
    "path": "demo11/pkg/errno/code.go",
    "content": "package errno\n\nvar (\n\t// Common errors\n\tOK                  = &Errno{Code: 0, Message: \"OK\"}\n\tInternalServerError = &Errno{Code: 10001, Message: \"Internal server error\"}\n\tErrBind             = &Errno{Code: 10002, Message: \"Error occurred while binding the request body to the struct.\"}\n\n\tErrValidation = &Errno{Code: 20001, Message: \"Validation failed.\"}\n\tErrDatabase   = &Errno{Code: 20002, Message: \"Database error.\"}\n\tErrToken      = &Errno{Code: 20003, Message: \"Error occurred while signing the JSON web token.\"}\n\n\t// user errors\n\tErrEncrypt           = &Errno{Code: 20101, Message: \"Error occurred while encrypting the user password.\"}\n\tErrUserNotFound      = &Errno{Code: 20102, Message: \"The user was not found.\"}\n\tErrTokenInvalid      = &Errno{Code: 20103, Message: \"The token was invalid.\"}\n\tErrPasswordIncorrect = &Errno{Code: 20104, Message: \"The password was incorrect.\"}\n)\n"
  },
  {
    "path": "demo11/pkg/errno/errno.go",
    "content": "package errno\n\nimport \"fmt\"\n\ntype Errno struct {\n\tCode    int\n\tMessage string\n}\n\nfunc (err Errno) Error() string {\n\treturn err.Message\n}\n\n// Err represents an error\ntype Err struct {\n\tCode    int\n\tMessage string\n\tErr     error\n}\n\nfunc New(errno *Errno, err error) *Err {\n\treturn &Err{Code: errno.Code, Message: errno.Message, Err: err}\n}\n\nfunc (err *Err) Add(message string) error {\n\terr.Message += \" \" + message\n\treturn err\n}\n\nfunc (err *Err) Addf(format string, args ...interface{}) error {\n\terr.Message += \" \" + fmt.Sprintf(format, args...)\n\treturn err\n}\n\nfunc (err *Err) Error() string {\n\treturn fmt.Sprintf(\"Err - code: %d, message: %s, error: %s\", err.Code, err.Message, err.Err)\n}\n\nfunc IsErrUserNotFound(err error) bool {\n\tcode, _ := DecodeErr(err)\n\treturn code == ErrUserNotFound.Code\n}\n\nfunc DecodeErr(err error) (int, string) {\n\tif err == nil {\n\t\treturn OK.Code, OK.Message\n\t}\n\n\tswitch typed := err.(type) {\n\tcase *Err:\n\t\treturn typed.Code, typed.Message\n\tcase *Errno:\n\t\treturn typed.Code, typed.Message\n\tdefault:\n\t}\n\n\treturn InternalServerError.Code, err.Error()\n}\n"
  },
  {
    "path": "demo11/pkg/token/token.go",
    "content": "package token\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\tjwt \"github.com/dgrijalva/jwt-go\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\t// ErrMissingHeader means the `Authorization` header was empty.\n\tErrMissingHeader = errors.New(\"The length of the `Authorization` header is zero.\")\n)\n\n// Context is the context of the JSON web token.\ntype Context struct {\n\tID       uint64\n\tUsername string\n}\n\n// secretFunc validates the secret format.\nfunc secretFunc(secret string) jwt.Keyfunc {\n\treturn func(token *jwt.Token) (interface{}, error) {\n\t\t// Make sure the `alg` is what we except.\n\t\tif _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {\n\t\t\treturn nil, jwt.ErrSignatureInvalid\n\t\t}\n\n\t\treturn []byte(secret), nil\n\t}\n}\n\n// Parse validates the token with the specified secret,\n// and returns the context if the token was valid.\nfunc Parse(tokenString string, secret string) (*Context, error) {\n\tctx := &Context{}\n\n\t// Parse the token.\n\ttoken, err := jwt.Parse(tokenString, secretFunc(secret))\n\n\t// Parse error.\n\tif err != nil {\n\t\treturn ctx, err\n\n\t\t// Read the token if it's valid.\n\t} else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {\n\t\tctx.ID = uint64(claims[\"id\"].(float64))\n\t\tctx.Username = claims[\"username\"].(string)\n\t\treturn ctx, nil\n\n\t\t// Other errors.\n\t} else {\n\t\treturn ctx, err\n\t}\n}\n\n// ParseRequest gets the token from the header and\n// pass it to the Parse function to parses the token.\nfunc ParseRequest(c *gin.Context) (*Context, error) {\n\theader := c.Request.Header.Get(\"Authorization\")\n\n\t// Load the jwt secret from config\n\tsecret := viper.GetString(\"jwt_secret\")\n\n\tif len(header) == 0 {\n\t\treturn &Context{}, ErrMissingHeader\n\t}\n\n\tvar t string\n\t// Parse the header to get the token part.\n\tfmt.Sscanf(header, \"Bearer %s\", &t)\n\treturn Parse(t, secret)\n}\n\n// Sign signs the context with the specified secret.\nfunc Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) {\n\t// Load the jwt secret from the Gin config if the secret isn't specified.\n\tif secret == \"\" {\n\t\tsecret = viper.GetString(\"jwt_secret\")\n\t}\n\t// The token content.\n\ttoken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{\n\t\t\"id\":       c.ID,\n\t\t\"username\": c.Username,\n\t\t\"nbf\":      time.Now().Unix(),\n\t\t\"iat\":      time.Now().Unix(),\n\t})\n\t// Sign the token with the specified secret.\n\ttokenString, err = token.SignedString([]byte(secret))\n\n\treturn\n}\n"
  },
  {
    "path": "demo11/router/middleware/auth.go",
    "content": "package middleware\n\nimport (\n\t\"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/pkg/token\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc AuthMiddleware() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// Parse the json web token.\n\t\tif _, err := token.ParseRequest(c); err != nil {\n\t\t\thandler.SendResponse(c, errno.ErrTokenInvalid, nil)\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "demo11/router/middleware/header.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// NoCache is a middleware function that appends headers\n// to prevent the client from caching the HTTP response.\nfunc NoCache(c *gin.Context) {\n\tc.Header(\"Cache-Control\", \"no-cache, no-store, max-age=0, must-revalidate, value\")\n\tc.Header(\"Expires\", \"Thu, 01 Jan 1970 00:00:00 GMT\")\n\tc.Header(\"Last-Modified\", time.Now().UTC().Format(http.TimeFormat))\n\tc.Next()\n}\n\n// Options is a middleware function that appends headers\n// for options requests and aborts then exits the middleware\n// chain and ends the request.\nfunc Options(c *gin.Context) {\n\tif c.Request.Method != \"OPTIONS\" {\n\t\tc.Next()\n\t} else {\n\t\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\t\tc.Header(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Access-Control-Allow-Headers\", \"authorization, origin, content-type, accept\")\n\t\tc.Header(\"Allow\", \"HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Content-Type\", \"application/json\")\n\t\tc.AbortWithStatus(200)\n\t}\n}\n\n// Secure is a middleware function that appends security\n// and resource access headers.\nfunc Secure(c *gin.Context) {\n\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\tc.Header(\"X-Frame-Options\", \"DENY\")\n\tc.Header(\"X-Content-Type-Options\", \"nosniff\")\n\tc.Header(\"X-XSS-Protection\", \"1; mode=block\")\n\tif c.Request.TLS != nil {\n\t\tc.Header(\"Strict-Transport-Security\", \"max-age=31536000\")\n\t}\n\n\t// Also consider adding Content-Security-Policy headers\n\t// c.Header(\"Content-Security-Policy\", \"script-src 'self' https://cdnjs.cloudflare.com\")\n}\n"
  },
  {
    "path": "demo11/router/middleware/logging.go",
    "content": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/willf/pad\"\n)\n\ntype bodyLogWriter struct {\n\tgin.ResponseWriter\n\tbody *bytes.Buffer\n}\n\nfunc (w bodyLogWriter) Write(b []byte) (int, error) {\n\tw.body.Write(b)\n\treturn w.ResponseWriter.Write(b)\n}\n\n// Logging is a middleware function that logs the each request.\nfunc Logging() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tstart := time.Now().UTC()\n\t\tpath := c.Request.URL.Path\n\n\t\treg := regexp.MustCompile(\"(/v1/user|/login)\")\n\t\tif !reg.MatchString(path) {\n\t\t\treturn\n\t\t}\n\n\t\t// Skip for the health check requests.\n\t\tif path == \"/sd/health\" || path == \"/sd/ram\" || path == \"/sd/cpu\" || path == \"/sd/disk\" {\n\t\t\treturn\n\t\t}\n\n\t\t// Read the Body content\n\t\tvar bodyBytes []byte\n\t\tif c.Request.Body != nil {\n\t\t\tbodyBytes, _ = ioutil.ReadAll(c.Request.Body)\n\t\t}\n\n\t\t// Restore the io.ReadCloser to its original state\n\t\tc.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))\n\n\t\t// The basic informations.\n\t\tmethod := c.Request.Method\n\t\tip := c.ClientIP()\n\n\t\t//log.Debugf(\"New request come in, path: %s, Method: %s, body `%s`\", path, method, string(bodyBytes))\n\t\tblw := &bodyLogWriter{\n\t\t\tbody:           bytes.NewBufferString(\"\"),\n\t\t\tResponseWriter: c.Writer,\n\t\t}\n\t\tc.Writer = blw\n\n\t\t// Continue.\n\t\tc.Next()\n\n\t\t// Calculates the latency.\n\t\tend := time.Now().UTC()\n\t\tlatency := end.Sub(start)\n\n\t\tcode, message := -1, \"\"\n\n\t\t// get code and message\n\t\tvar response handler.Response\n\t\tif err := json.Unmarshal(blw.body.Bytes(), &response); err != nil {\n\t\t\tlog.Errorf(err, \"response body can not unmarshal to model.Response struct, body: `%s`\", blw.body.Bytes())\n\t\t\tcode = errno.InternalServerError.Code\n\t\t\tmessage = err.Error()\n\t\t} else {\n\t\t\tcode = response.Code\n\t\t\tmessage = response.Message\n\t\t}\n\n\t\tlog.Infof(\"%-13s | %-12s | %s %s | {code: %d, message: %s}\", latency, ip, pad.Right(method, 5, \"\"), path, code, message)\n\t}\n}\n"
  },
  {
    "path": "demo11/router/middleware/requestid.go",
    "content": "package middleware\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/satori/go.uuid\"\n)\n\nfunc RequestId() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// Check for incoming header, use it if exists\n\t\trequestId := c.Request.Header.Get(\"X-Request-Id\")\n\n\t\t// Create request id with UUID4\n\t\tif requestId == \"\" {\n\t\t\tu4, _ := uuid.NewV4()\n\t\t\trequestId = u4.String()\n\t\t}\n\n\t\t// Expose it for use in the application\n\t\tc.Set(\"X-Request-Id\", requestId)\n\n\t\t// Set X-Request-Id header\n\t\tc.Writer.Header().Set(\"X-Request-Id\", requestId)\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "demo11/router/router.go",
    "content": "package router\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/handler/sd\"\n\t\"apiserver/handler/user\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Load loads the middlewares, routes, handlers.\nfunc Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {\n\t// Middlewares.\n\tg.Use(gin.Recovery())\n\tg.Use(middleware.NoCache)\n\tg.Use(middleware.Options)\n\tg.Use(middleware.Secure)\n\tg.Use(mw...)\n\t// 404 Handler.\n\tg.NoRoute(func(c *gin.Context) {\n\t\tc.String(http.StatusNotFound, \"The incorrect API route.\")\n\t})\n\n\t// api for authentication functionalities\n\tg.POST(\"/login\", user.Login)\n\n\t// The user handlers, requiring authentication\n\tu := g.Group(\"/v1/user\")\n\tu.Use(middleware.AuthMiddleware())\n\t{\n\t\tu.POST(\"\", user.Create)\n\t\tu.DELETE(\"/:id\", user.Delete)\n\t\tu.PUT(\"/:id\", user.Update)\n\t\tu.GET(\"\", user.List)\n\t\tu.GET(\"/:username\", user.Get)\n\t}\n\n\t// The health check handlers\n\tsvcd := g.Group(\"/sd\")\n\t{\n\t\tsvcd.GET(\"/health\", sd.HealthCheck)\n\t\tsvcd.GET(\"/disk\", sd.DiskCheck)\n\t\tsvcd.GET(\"/cpu\", sd.CPUCheck)\n\t\tsvcd.GET(\"/ram\", sd.RAMCheck)\n\t}\n\n\treturn g\n}\n"
  },
  {
    "path": "demo11/service/service.go",
    "content": "package service\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\n\t\"apiserver/model\"\n\t\"apiserver/util\"\n)\n\nfunc ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) {\n\tinfos := make([]*model.UserInfo, 0)\n\tusers, count, err := model.ListUser(username, offset, limit)\n\tif err != nil {\n\t\treturn nil, count, err\n\t}\n\n\tids := []uint64{}\n\tfor _, user := range users {\n\t\tids = append(ids, user.Id)\n\t}\n\n\twg := sync.WaitGroup{}\n\tuserList := model.UserList{\n\t\tLock:  new(sync.Mutex),\n\t\tIdMap: make(map[uint64]*model.UserInfo, len(users)),\n\t}\n\n\terrChan := make(chan error, 1)\n\tfinished := make(chan bool, 1)\n\n\t// Improve query efficiency in parallel\n\tfor _, u := range users {\n\t\twg.Add(1)\n\t\tgo func(u *model.UserModel) {\n\t\t\tdefer wg.Done()\n\n\t\t\tshortId, err := util.GenShortId()\n\t\t\tif err != nil {\n\t\t\t\terrChan <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tuserList.Lock.Lock()\n\t\t\tdefer userList.Lock.Unlock()\n\t\t\tuserList.IdMap[u.Id] = &model.UserInfo{\n\t\t\t\tId:        u.Id,\n\t\t\t\tUsername:  u.Username,\n\t\t\t\tSayHello:  fmt.Sprintf(\"Hello %s\", shortId),\n\t\t\t\tPassword:  u.Password,\n\t\t\t\tCreatedAt: u.CreatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t\tUpdatedAt: u.UpdatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t}\n\t\t}(u)\n\t}\n\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(finished)\n\t}()\n\n\tselect {\n\tcase <-finished:\n\tcase err := <-errChan:\n\t\treturn nil, count, err\n\t}\n\n\tfor _, id := range ids {\n\t\tinfos = append(infos, userList.IdMap[id])\n\t}\n\n\treturn infos, count, nil\n}\n"
  },
  {
    "path": "demo11/util/util.go",
    "content": "package util\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/teris-io/shortid\"\n)\n\nfunc GenShortId() (string, error) {\n\treturn shortid.Generate()\n}\n\nfunc GetReqID(c *gin.Context) string {\n\tv, ok := c.Get(\"X-Request-Id\")\n\tif !ok {\n\t\treturn \"\"\n\t}\n\tif requestId, ok := v.(string); ok {\n\t\treturn requestId\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "demo12/Makefile",
    "content": "SHELL := /bin/bash\nBASEDIR = $(shell pwd)\n\n# build with verison infos\nversionDir = \"apiserver/pkg/version\"\ngitTag = $(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)\nbuildDate = $(shell TZ=Asia/Shanghai date +%FT%T%z)\ngitCommit = $(shell git log --pretty=format:'%H' -n 1)\ngitTreeState = $(shell if git status|grep -q 'clean';then echo clean; else echo dirty; fi)\n\nldflags=\"-w -X ${versionDir}.gitTag=${gitTag} -X ${versionDir}.buildDate=${buildDate} -X ${versionDir}.gitCommit=${gitCommit} -X ${versionDir}.gitTreeState=${gitTreeState}\"\n\nall: gotool\n\t@go build -v -ldflags ${ldflags} .\nclean:\n\trm -f apiserver\n\tfind . -name \"[._]*.s[a-w][a-z]\" | xargs -i rm -f {}\ngotool:\n\tgofmt -w .\n\tgo tool vet . |& grep -v vendor;true\nca:\n\topenssl 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\"\n\nhelp:\n\t@echo \"make - compile the source code\"\n\t@echo \"make clean - remove binary file and vim swp files\"\n\t@echo \"make gotool - run go tool 'fmt' and 'vet'\"\n\t@echo \"make ca - generate ca files\"\n\n.PHONY: clean gotool ca help\n\n\n"
  },
  {
    "path": "demo12/README.md",
    "content": "进阶：给API命令增加版本功能\n"
  },
  {
    "path": "demo12/conf/config.yaml",
    "content": "runmode: debug                 # 开发模式, debug, release, test\naddr: :8080                  # HTTP绑定端口\nname: apiserver              # API Server的名字\nurl: http://127.0.0.1:8080   # pingServer函数请求的API服务器的ip:port\nmax_ping_count: 10           # pingServer函数try的次数\njwt_secret: Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5\ntls:\n  addr: :8081\n  cert: conf/server.crt\n  key: conf/server.key\nlog:\n  writers: stdout\n  logger_level: DEBUG\n  logger_file: log/apiserver.log\n  log_format_text: true\n  rollingPolicy: size\n  log_rotate_date: 1\n  log_rotate_size: 1\n  log_backup_count: 7\ndb:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\ndocker_db:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\n"
  },
  {
    "path": "demo12/conf/server.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIID2TCCAsGgAwIBAgIJAI/8axTNy37vMA0GCSqGSIb3DQEBBQUAMIGCMQswCQYD\nVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO\nUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAkxMjcuMC4wLjEx\nGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNvbTAeFw0xODA2MDQwNjMwMzFaFw0y\nODA2MDEwNjMwMzFaMIGCMQswCQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYD\nVQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklU\nMRIwEAYDVQQDDAkxMjcuMC4wLjExGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNv\nbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKHbnpmaxHokFC8rnJlG\n42eL4C3tog7rffv96DrwDajdFNk8DeaNUpZ+R4X4YPecumsCsbol2dDC1IHHGJUN\nq/A1vRjyDptMLmzp30p1aqVPcz+j8GMXtZUflLgup+HeKZRdrsk/0SaH1C3ywUBm\nyDMNHUxEvoFj0x9aJoPutnkEk9miZt/kF2vxlTbKFM14/TecVr3ljNwTebJAqEw9\niaZpNXn/IQV2I2PTGlxjyT6XNsCchjMTJ9y/XS9rBeX6BFHgQsV52CW1mWz8FieM\n/nyF9hE/TZKCpMtHWEYIdBbrxxY0BlhMxEtyIFzTha5zltFrQJGUYz7RIlyuabjk\nor8CAwEAAaNQME4wHQYDVR0OBBYEFIjLBQlToqUDoyOLeUd6HeaqiiLJMB8GA1Ud\nIwQYMBaAFIjLBQlToqUDoyOLeUd6HeaqiiLJMAwGA1UdEwQFMAMBAf8wDQYJKoZI\nhvcNAQEFBQADggEBAHAqpqGNUNgYCr3JvrVULDqFKmqLpvDKfLd6kmtnFzvuZLSz\nANE2RXXqQ5Ms3F0oHqZQq4aOvgvfas5dBLoToPL/ip+GibQ7O/1PsPD5bZaczUel\nAnmClX4ewcOEWmZx9mbYOz2C8RREvKU3MoBERrWA33LquYH0+8W8WHyMKNNF97oD\nBgWZ8R1t3N59ySZyHcye7ddlJUubRbdyCGrHbmziHV3Cj61NJiwS2JRvhYkc71M6\n8921j63qEM6i0NusFYG/w0GM3x/OeaeY/om6x+IOhJp3vIa1JPSekVyNSCwDEc3B\n+8WmVFaeMMXwErSkuu8nCbqRP8J1jiKgzpG/92g=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "demo12/conf/server.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCh256ZmsR6JBQv\nK5yZRuNni+At7aIO6337/eg68A2o3RTZPA3mjVKWfkeF+GD3nLprArG6JdnQwtSB\nxxiVDavwNb0Y8g6bTC5s6d9KdWqlT3M/o/BjF7WVH5S4Lqfh3imUXa7JP9Emh9Qt\n8sFAZsgzDR1MRL6BY9MfWiaD7rZ5BJPZombf5Bdr8ZU2yhTNeP03nFa95YzcE3my\nQKhMPYmmaTV5/yEFdiNj0xpcY8k+lzbAnIYzEyfcv10vawXl+gRR4ELFedgltZls\n/BYnjP58hfYRP02SgqTLR1hGCHQW68cWNAZYTMRLciBc04Wuc5bRa0CRlGM+0SJc\nrmm45KK/AgMBAAECggEASNMWvgf7pPT8u+iEchaKFLnDqQaFZu8f5TRtu67shnDK\ng59YpcYqRZoVtjp17pLu8Vzp+FY1dY9jq+yXq+DV3qNfLI0kc01IiiqEE+1WiYCA\n2z541y0Av1LRSDl9wcuCq8Wm8der1AlDN1VFDCPyqb2Z1AoOKQtwH2ghcjUCltn4\nNxuMYOVdql+Idl/7PCEha9pW3DQvPqJVXRPvBh6tB7FFuUOIq4+EFRUWWZlyCJfQ\nfYxB9jcFUWmAUieV3iBajg6H6QVkj/STLSgKuSktEBM2mqyL4slHXFWRt7+NQikg\ntOEWnqcTC4Ei2Dyam4SBXHJ1wT8AJV4kMTrN1ZBRmQKBgQDOMcIGHyuBI1aHcvPW\nTkein7bslFAKsA1LBqnw+alWzSHMKEmfWbBjeW0FQTIGEYMXwIq5JhnNcS5Kb5Qt\nBZgbBPDb7S80DtJnkno3iJ6hpijE3X/29lezigIuGibO5MD0Na1am2BP0iU77ifV\nxpYvj/j/pkdcTLM7hRDImSeNIwKBgQDI9EUFkGi6H9NZEles+MCHenVRYUBT2PXe\nhmtd9d5f4v3CmRsXvl4Dl/slIrJ9kuZktAck3Ocx6D0hnZcj1mwq077K7H3QqKCl\nzRwB11H5v/imsZ1ey0hLVPhCzr2miRPM/9oL5B2st9eotzhyRcrMn+oYZSSt79S8\nQ3YDiZ7TtQKBgENSr7z79GJnvVrgR4kTagRJDZrVGgVDUjPK6zXI7mdu9rgH93HW\nAOeZv+TVUpX0pc7diO3G6OnRKIIZSFIi33UC+fl0ydK/fCdhBhKXwuOYsvsEL0Hd\nUOlICEoxM7adrfqOhBlvXdTyEkItEkiUXHkPEwe1rNsQF/05By/YAbftAoGAYB2F\njd2+WZezTN0bFl58J9CIoH31eKVDJEYCwJRC4nX9jcARV0/0Q5/DvcVUvf8vN2ds\nK1OFOTetVZC8o6WBYxKYJRLsMosVG3h5NuA4E06grYoyjQ6J644emEWuLCNQVzLg\npeNb1iqweb/4vZ9oGms6WqS14IPfqpRRs+t1DikCgYEAyFtQKlLmNm/EirOMqjS2\n4/zMRj4B6n8BB8ZJsVC/MkuD+4B03rY44pP324cJnqVIcug9Zoijffnh1qbtZEc/\nMhNCa4a2MY0qcd4r/43cGbjqv94En7e5TgkH/79W+lbR3uBayt33RuuKStauZkf/\n2eBi4LlGO5stoeD63h9Ten0=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "demo12/config/config.go",
    "content": "package config\n\nimport (\n\t\"strings\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n)\n\ntype Config struct {\n\tName string\n}\n\nfunc Init(cfg string) error {\n\tc := Config{\n\t\tName: cfg,\n\t}\n\n\t// 初始化配置文件\n\tif err := c.initConfig(); err != nil {\n\t\treturn err\n\t}\n\n\t// 初始化日志包\n\tc.initLog()\n\n\t// 监控配置文件变化并热加载程序\n\tc.watchConfig()\n\n\treturn nil\n}\n\nfunc (c *Config) initConfig() error {\n\tif c.Name != \"\" {\n\t\tviper.SetConfigFile(c.Name) // 如果指定了配置文件，则解析指定的配置文件\n\t} else {\n\t\tviper.AddConfigPath(\"conf\") // 如果没有指定配置文件，则解析默认的配置文件\n\t\tviper.SetConfigName(\"config\")\n\t}\n\tviper.SetConfigType(\"yaml\")     // 设置配置文件格式为YAML\n\tviper.AutomaticEnv()            // 读取匹配的环境变量\n\tviper.SetEnvPrefix(\"APISERVER\") // 读取环境变量的前缀为APISERVER\n\treplacer := strings.NewReplacer(\".\", \"_\")\n\tviper.SetEnvKeyReplacer(replacer)\n\tif err := viper.ReadInConfig(); err != nil { // viper解析配置文件\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *Config) initLog() {\n\tpassLagerCfg := log.PassLagerCfg{\n\t\tWriters:        viper.GetString(\"log.writers\"),\n\t\tLoggerLevel:    viper.GetString(\"log.logger_level\"),\n\t\tLoggerFile:     viper.GetString(\"log.logger_file\"),\n\t\tLogFormatText:  viper.GetBool(\"log.log_format_text\"),\n\t\tRollingPolicy:  viper.GetString(\"log.rollingPolicy\"),\n\t\tLogRotateDate:  viper.GetInt(\"log.log_rotate_date\"),\n\t\tLogRotateSize:  viper.GetInt(\"log.log_rotate_size\"),\n\t\tLogBackupCount: viper.GetInt(\"log.log_backup_count\"),\n\t}\n\n\tlog.InitWithConfig(&passLagerCfg)\n}\n\n// 监控配置文件变化并热加载程序\nfunc (c *Config) watchConfig() {\n\tviper.WatchConfig()\n\tviper.OnConfigChange(func(e fsnotify.Event) {\n\t\tlog.Infof(\"Config file changed: %s\", e.Name)\n\t})\n}\n"
  },
  {
    "path": "demo12/db.sql",
    "content": "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n/*!40101 SET NAMES utf8 */;\n/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;\n/*!40103 SET TIME_ZONE='+00:00' */;\n/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;\n/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n\nCREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */;\n\nUSE `db_apiserver`;\n\n--\n-- Table structure for table `tb_users`\n--\n\nDROP TABLE IF EXISTS `tb_users`;\n/*!40101 SET @saved_cs_client     = @@character_set_client */;\n/*!40101 SET character_set_client = utf8 */;\nCREATE TABLE `tb_users` (\n  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n  `username` varchar(255) NOT NULL,\n  `password` varchar(255) NOT NULL,\n  `createdAt` timestamp NULL DEFAULT NULL,\n  `updatedAt` timestamp NULL DEFAULT NULL,\n  `deletedAt` timestamp NULL DEFAULT NULL,\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `username` (`username`),\n  KEY `idx_tb_users_deletedAt` (`deletedAt`)\n) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;\n/*!40101 SET character_set_client = @saved_cs_client */;\n\n--\n-- Dumping data for table `tb_users`\n--\n\nLOCK TABLES `tb_users` WRITE;\n/*!40000 ALTER TABLE `tb_users` DISABLE KEYS */;\nINSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL);\n/*!40000 ALTER TABLE `tb_users` ENABLE KEYS */;\nUNLOCK TABLES;\n/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;\n\n/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;\n/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;\n/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n\n-- Dump completed on 2018-05-28  0:25:41\n"
  },
  {
    "path": "demo12/handler/handler.go",
    "content": "package handler\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype Response struct {\n\tCode    int         `json:\"code\"`\n\tMessage string      `json:\"message\"`\n\tData    interface{} `json:\"data\"`\n}\n\nfunc SendResponse(c *gin.Context, err error, data interface{}) {\n\tcode, message := errno.DecodeErr(err)\n\n\t// always return http.StatusOK\n\tc.JSON(http.StatusOK, Response{\n\t\tCode:    code,\n\t\tMessage: message,\n\t\tData:    data,\n\t})\n}\n"
  },
  {
    "path": "demo12/handler/sd/check.go",
    "content": "package sd\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/shirou/gopsutil/cpu\"\n\t\"github.com/shirou/gopsutil/disk\"\n\t\"github.com/shirou/gopsutil/load\"\n\t\"github.com/shirou/gopsutil/mem\"\n)\n\nconst (\n\tB  = 1\n\tKB = 1024 * B\n\tMB = 1024 * KB\n\tGB = 1024 * MB\n)\n\n// HealthCheck shows `OK` as the ping-pong result.\nfunc HealthCheck(c *gin.Context) {\n\tmessage := \"OK\"\n\tc.String(http.StatusOK, \"\\n\"+message)\n}\n\n// DiskCheck checks the disk usage.\nfunc DiskCheck(c *gin.Context) {\n\tu, _ := disk.Usage(\"/\")\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusOK\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n\n// CPUCheck checks the cpu usage.\nfunc CPUCheck(c *gin.Context) {\n\tcores, _ := cpu.Counts(false)\n\n\ta, _ := load.Avg()\n\tl1 := a.Load1\n\tl5 := a.Load5\n\tl15 := a.Load15\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif l5 >= float64(cores-1) {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if l5 >= float64(cores-2) {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Load average: %.2f, %.2f, %.2f | Cores: %d\", text, l1, l5, l15, cores)\n\tc.String(status, \"\\n\"+message)\n}\n\n// RAMCheck checks the disk usage.\nfunc RAMCheck(c *gin.Context) {\n\tu, _ := mem.VirtualMemory()\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n"
  },
  {
    "path": "demo12/handler/user/create.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// Create creates a new user account.\nfunc Create(c *gin.Context) {\n\tlog.Info(\"User Create function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\tvar r CreateRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tu := model.UserModel{\n\t\tUsername: r.Username,\n\t\tPassword: r.Password,\n\t}\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\t// Insert the user to the database.\n\tif err := u.Create(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\trsp := CreateResponse{\n\t\tUsername: r.Username,\n\t}\n\n\t// Show the user information.\n\tSendResponse(c, nil, rsp)\n}\n"
  },
  {
    "path": "demo12/handler/user/delete.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Delete delete an user by the user identifier.\nfunc Delete(c *gin.Context) {\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\tif err := model.DeleteUser(uint64(userId)); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo12/handler/user/get.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Get gets an user by the user identifier.\nfunc Get(c *gin.Context) {\n\tusername := c.Param(\"username\")\n\t// Get the user by the `username` from the database.\n\tuser, err := model.GetUser(username)\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrUserNotFound, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, user)\n}\n"
  },
  {
    "path": "demo12/handler/user/list.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/service\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// List list the users in the database.\nfunc List(c *gin.Context) {\n\tvar r ListRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tinfos, count, err := service.ListUser(r.Username, r.Offset, r.Limit)\n\tif err != nil {\n\t\tSendResponse(c, err, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, ListResponse{\n\t\tTotalCount: count,\n\t\tUserList:   infos,\n\t})\n}\n"
  },
  {
    "path": "demo12/handler/user/login.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/auth\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/pkg/token\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Login generates the authentication token\n// if the password was matched with the specified account.\nfunc Login(c *gin.Context) {\n\t// Binding the data with the user struct.\n\tvar u model.UserModel\n\tif err := c.Bind(&u); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\t// Get the user information by the login username.\n\td, err := model.GetUser(u.Username)\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrUserNotFound, nil)\n\t\treturn\n\t}\n\n\t// Compare the login password with the user password.\n\tif err := auth.Compare(d.Password, u.Password); err != nil {\n\t\tSendResponse(c, errno.ErrPasswordIncorrect, nil)\n\t\treturn\n\t}\n\n\t// Sign the json web token.\n\tt, err := token.Sign(c, token.Context{ID: d.Id, Username: d.Username}, \"\")\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrToken, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, model.Token{Token: t})\n}\n"
  },
  {
    "path": "demo12/handler/user/update.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// Update update a exist user account info.\nfunc Update(c *gin.Context) {\n\tlog.Info(\"Update function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\t// Get the user id from the url parameter.\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\n\t// Binding the user data.\n\tvar u model.UserModel\n\tif err := c.Bind(&u); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\t// We update the record based on the user id.\n\tu.Id = uint64(userId)\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\n\t// Save changed fields.\n\tif err := u.Update(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo12/handler/user/user.go",
    "content": "package user\n\nimport (\n\t\"apiserver/model\"\n)\n\ntype CreateRequest struct {\n\tUsername string `json:\"username\"`\n\tPassword string `json:\"password\"`\n}\n\ntype CreateResponse struct {\n\tUsername string `json:\"username\"`\n}\n\ntype ListRequest struct {\n\tUsername string `json:\"username\"`\n\tOffset   int    `json:\"offset\"`\n\tLimit    int    `json:\"limit\"`\n}\n\ntype ListResponse struct {\n\tTotalCount uint64            `json:\"totalCount\"`\n\tUserList   []*model.UserInfo `json:\"userList\"`\n}\n"
  },
  {
    "path": "demo12/main.go",
    "content": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n\n\t\"apiserver/config\"\n\t\"apiserver/model\"\n\tv \"apiserver/pkg/version\"\n\t\"apiserver/router\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tcfg     = pflag.StringP(\"config\", \"c\", \"\", \"apiserver config file path.\")\n\tversion = pflag.BoolP(\"version\", \"v\", false, \"show version info.\")\n)\n\nfunc main() {\n\tpflag.Parse()\n\tif *version {\n\t\tv := v.Get()\n\t\tmarshalled, err := json.MarshalIndent(&v, \"\", \"  \")\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"%v\\n\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tfmt.Println(string(marshalled))\n\t\treturn\n\t}\n\n\t// init config\n\tif err := config.Init(*cfg); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// init db\n\tmodel.DB.Init()\n\tdefer model.DB.Close()\n\n\t// Set gin mode.\n\tgin.SetMode(viper.GetString(\"runmode\"))\n\n\t// Create the Gin engine.\n\tg := gin.New()\n\n\t// Routes.\n\trouter.Load(\n\t\t// Cores.\n\t\tg,\n\n\t\t// Middlwares.\n\t\tmiddleware.Logging(),\n\t\tmiddleware.RequestId(),\n\t)\n\n\t// Ping the server to make sure the router is working.\n\tgo func() {\n\t\tif err := pingServer(); err != nil {\n\t\t\tlog.Fatal(\"The router has no response, or it might took too long to start up.\", err)\n\t\t}\n\t\tlog.Info(\"The router has been deployed successfully.\")\n\t}()\n\n\t// Start to listening the incoming requests.\n\tcert := viper.GetString(\"tls.cert\")\n\tkey := viper.GetString(\"tls.key\")\n\tif cert != \"\" && key != \"\" {\n\t\tgo func() {\n\t\t\tlog.Infof(\"Start to listening the incoming requests on https address: %s\", viper.GetString(\"tls.addr\"))\n\t\t\tlog.Info(http.ListenAndServeTLS(viper.GetString(\"tls.addr\"), cert, key, g).Error())\n\t\t}()\n\t}\n\n\tlog.Infof(\"Start to listening the incoming requests on http address: %s\", viper.GetString(\"addr\"))\n\tlog.Info(http.ListenAndServe(viper.GetString(\"addr\"), g).Error())\n}\n\n// pingServer pings the http server to make sure the router is working.\nfunc pingServer() error {\n\tfor i := 0; i < viper.GetInt(\"max_ping_count\"); i++ {\n\t\t// Ping the server by sending a GET request to `/health`.\n\t\tresp, err := http.Get(viper.GetString(\"url\") + \"/sd/health\")\n\t\tif err == nil && resp.StatusCode == 200 {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Sleep for a second to continue the next ping.\n\t\tlog.Info(\"Waiting for the router, retry in 1 second.\")\n\t\ttime.Sleep(time.Second)\n\t}\n\treturn errors.New(\"Cannot connect to the router.\")\n}\n"
  },
  {
    "path": "demo12/model/init.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n\t// MySQL driver.\n\t\"github.com/jinzhu/gorm\"\n\t_ \"github.com/jinzhu/gorm/dialects/mysql\"\n)\n\ntype Database struct {\n\tSelf   *gorm.DB\n\tDocker *gorm.DB\n}\n\nvar DB *Database\n\nfunc openDB(username, password, addr, name string) *gorm.DB {\n\tconfig := fmt.Sprintf(\"%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s\",\n\t\tusername,\n\t\tpassword,\n\t\taddr,\n\t\tname,\n\t\ttrue,\n\t\t//\"Asia/Shanghai\"),\n\t\t\"Local\")\n\n\tdb, err := gorm.Open(\"mysql\", config)\n\tif err != nil {\n\t\tlog.Errorf(err, \"Database connection failed. Database name: %s\", name)\n\t}\n\n\t// set for db connection\n\tsetupDB(db)\n\n\treturn db\n}\n\nfunc setupDB(db *gorm.DB) {\n\tdb.LogMode(viper.GetBool(\"gormlog\"))\n\t//db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数，默认值为0表示不限制.设置最大的连接数，可以避免并发太高导致连接mysql出现too many connections的错误。\n\tdb.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。\n}\n\n// used for cli\nfunc InitSelfDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"db.username\"),\n\t\tviper.GetString(\"db.password\"),\n\t\tviper.GetString(\"db.addr\"),\n\t\tviper.GetString(\"db.name\"))\n}\n\nfunc GetSelfDB() *gorm.DB {\n\treturn InitSelfDB()\n}\n\nfunc InitDockerDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"docker_db.username\"),\n\t\tviper.GetString(\"docker_db.password\"),\n\t\tviper.GetString(\"docker_db.addr\"),\n\t\tviper.GetString(\"docker_db.name\"))\n}\n\nfunc GetDockerDB() *gorm.DB {\n\treturn InitDockerDB()\n}\n\nfunc (db *Database) Init() {\n\tDB = &Database{\n\t\tSelf:   GetSelfDB(),\n\t\tDocker: GetDockerDB(),\n\t}\n}\n\nfunc (db *Database) Close() {\n\tDB.Self.Close()\n\tDB.Docker.Close()\n}\n"
  },
  {
    "path": "demo12/model/model.go",
    "content": "package model\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype BaseModel struct {\n\tId        uint64     `gorm:\"primary_key;AUTO_INCREMENT;column:id\" json:\"-\"`\n\tCreatedAt time.Time  `gorm:\"column:createdAt\" json:\"-\"`\n\tUpdatedAt time.Time  `gorm:\"column:updatedAt\" json:\"-\"`\n\tDeletedAt *time.Time `gorm:\"column:deletedAt\" sql:\"index\" json:\"-\"`\n}\n\ntype UserInfo struct {\n\tId        uint64 `json:\"id\"`\n\tUsername  string `json:\"username\"`\n\tSayHello  string `json:\"sayHello\"`\n\tPassword  string `json:\"password\"`\n\tCreatedAt string `json:\"createdAt\"`\n\tUpdatedAt string `json:\"updatedAt\"`\n}\n\ntype UserList struct {\n\tLock  *sync.Mutex\n\tIdMap map[uint64]*UserInfo\n}\n\n// Token represents a JSON web token.\ntype Token struct {\n\tToken string `json:\"token\"`\n}\n"
  },
  {
    "path": "demo12/model/user.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"apiserver/pkg/auth\"\n\t\"apiserver/pkg/constvar\"\n\n\tvalidator \"gopkg.in/go-playground/validator.v9\"\n)\n\n// User represents a registered user.\ntype UserModel struct {\n\tBaseModel\n\tUsername string `json:\"username\" gorm:\"column:username;not null\" binding:\"required\" validate:\"min=1,max=32\"`\n\tPassword string `json:\"password\" gorm:\"column:password;not null\" binding:\"required\" validate:\"min=5,max=128\"`\n}\n\nfunc (c *UserModel) TableName() string {\n\treturn \"tb_users\"\n}\n\n// Create creates a new user account.\nfunc (u *UserModel) Create() error {\n\treturn DB.Self.Create(&u).Error\n}\n\n// DeleteUser deletes the user by the user identifier.\nfunc DeleteUser(id uint64) error {\n\tuser := UserModel{}\n\tuser.BaseModel.Id = id\n\treturn DB.Self.Delete(&user).Error\n}\n\n// Update updates an user account information.\nfunc (u *UserModel) Update() error {\n\treturn DB.Self.Save(u).Error\n}\n\n// GetUser gets an user by the user identifier.\nfunc GetUser(username string) (*UserModel, error) {\n\tu := &UserModel{}\n\td := DB.Self.Where(\"username = ?\", username).First(&u)\n\treturn u, d.Error\n}\n\n// ListUser List all users\nfunc ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) {\n\tif limit == 0 {\n\t\tlimit = constvar.DefaultLimit\n\t}\n\n\tusers := make([]*UserModel, 0)\n\tvar count uint64\n\n\twhere := fmt.Sprintf(\"username like '%%%s%%'\", username)\n\tif err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\tif err := DB.Self.Where(where).Offset(offset).Limit(limit).Order(\"id desc\").Find(&users).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\treturn users, count, nil\n}\n\n// Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct).\nfunc (u *UserModel) Compare(pwd string) (err error) {\n\terr = auth.Compare(u.Password, pwd)\n\treturn\n}\n\n// Encrypt the user password.\nfunc (u *UserModel) Encrypt() (err error) {\n\tu.Password, err = auth.Encrypt(u.Password)\n\treturn\n}\n\n// Validate the fields.\nfunc (u *UserModel) Validate() error {\n\tvalidate := validator.New()\n\treturn validate.Struct(u)\n}\n"
  },
  {
    "path": "demo12/pkg/auth/auth.go",
    "content": "package auth\n\nimport \"golang.org/x/crypto/bcrypt\"\n\n// Encrypt encrypts the plain text with bcrypt.\nfunc Encrypt(source string) (string, error) {\n\thashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost)\n\treturn string(hashedBytes), err\n}\n\n// Compare compares the encrypted text with the plain text if it's the same.\nfunc Compare(hashedPassword, password string) error {\n\treturn bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))\n}\n"
  },
  {
    "path": "demo12/pkg/constvar/constvar.go",
    "content": "package constvar\n\nconst (\n\tDefaultLimit = 50\n)\n"
  },
  {
    "path": "demo12/pkg/errno/code.go",
    "content": "package errno\n\nvar (\n\t// Common errors\n\tOK                  = &Errno{Code: 0, Message: \"OK\"}\n\tInternalServerError = &Errno{Code: 10001, Message: \"Internal server error\"}\n\tErrBind             = &Errno{Code: 10002, Message: \"Error occurred while binding the request body to the struct.\"}\n\n\tErrValidation = &Errno{Code: 20001, Message: \"Validation failed.\"}\n\tErrDatabase   = &Errno{Code: 20002, Message: \"Database error.\"}\n\tErrToken      = &Errno{Code: 20003, Message: \"Error occurred while signing the JSON web token.\"}\n\n\t// user errors\n\tErrEncrypt           = &Errno{Code: 20101, Message: \"Error occurred while encrypting the user password.\"}\n\tErrUserNotFound      = &Errno{Code: 20102, Message: \"The user was not found.\"}\n\tErrTokenInvalid      = &Errno{Code: 20103, Message: \"The token was invalid.\"}\n\tErrPasswordIncorrect = &Errno{Code: 20104, Message: \"The password was incorrect.\"}\n)\n"
  },
  {
    "path": "demo12/pkg/errno/errno.go",
    "content": "package errno\n\nimport \"fmt\"\n\ntype Errno struct {\n\tCode    int\n\tMessage string\n}\n\nfunc (err Errno) Error() string {\n\treturn err.Message\n}\n\n// Err represents an error\ntype Err struct {\n\tCode    int\n\tMessage string\n\tErr     error\n}\n\nfunc New(errno *Errno, err error) *Err {\n\treturn &Err{Code: errno.Code, Message: errno.Message, Err: err}\n}\n\nfunc (err *Err) Add(message string) error {\n\terr.Message += \" \" + message\n\treturn err\n}\n\nfunc (err *Err) Addf(format string, args ...interface{}) error {\n\terr.Message += \" \" + fmt.Sprintf(format, args...)\n\treturn err\n}\n\nfunc (err *Err) Error() string {\n\treturn fmt.Sprintf(\"Err - code: %d, message: %s, error: %s\", err.Code, err.Message, err.Err)\n}\n\nfunc IsErrUserNotFound(err error) bool {\n\tcode, _ := DecodeErr(err)\n\treturn code == ErrUserNotFound.Code\n}\n\nfunc DecodeErr(err error) (int, string) {\n\tif err == nil {\n\t\treturn OK.Code, OK.Message\n\t}\n\n\tswitch typed := err.(type) {\n\tcase *Err:\n\t\treturn typed.Code, typed.Message\n\tcase *Errno:\n\t\treturn typed.Code, typed.Message\n\tdefault:\n\t}\n\n\treturn InternalServerError.Code, err.Error()\n}\n"
  },
  {
    "path": "demo12/pkg/token/token.go",
    "content": "package token\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\tjwt \"github.com/dgrijalva/jwt-go\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\t// ErrMissingHeader means the `Authorization` header was empty.\n\tErrMissingHeader = errors.New(\"The length of the `Authorization` header is zero.\")\n)\n\n// Context is the context of the JSON web token.\ntype Context struct {\n\tID       uint64\n\tUsername string\n}\n\n// secretFunc validates the secret format.\nfunc secretFunc(secret string) jwt.Keyfunc {\n\treturn func(token *jwt.Token) (interface{}, error) {\n\t\t// Make sure the `alg` is what we except.\n\t\tif _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {\n\t\t\treturn nil, jwt.ErrSignatureInvalid\n\t\t}\n\n\t\treturn []byte(secret), nil\n\t}\n}\n\n// Parse validates the token with the specified secret,\n// and returns the context if the token was valid.\nfunc Parse(tokenString string, secret string) (*Context, error) {\n\tctx := &Context{}\n\n\t// Parse the token.\n\ttoken, err := jwt.Parse(tokenString, secretFunc(secret))\n\n\t// Parse error.\n\tif err != nil {\n\t\treturn ctx, err\n\n\t\t// Read the token if it's valid.\n\t} else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {\n\t\tctx.ID = uint64(claims[\"id\"].(float64))\n\t\tctx.Username = claims[\"username\"].(string)\n\t\treturn ctx, nil\n\n\t\t// Other errors.\n\t} else {\n\t\treturn ctx, err\n\t}\n}\n\n// ParseRequest gets the token from the header and\n// pass it to the Parse function to parses the token.\nfunc ParseRequest(c *gin.Context) (*Context, error) {\n\theader := c.Request.Header.Get(\"Authorization\")\n\n\t// Load the jwt secret from config\n\tsecret := viper.GetString(\"jwt_secret\")\n\n\tif len(header) == 0 {\n\t\treturn &Context{}, ErrMissingHeader\n\t}\n\n\tvar t string\n\t// Parse the header to get the token part.\n\tfmt.Sscanf(header, \"Bearer %s\", &t)\n\treturn Parse(t, secret)\n}\n\n// Sign signs the context with the specified secret.\nfunc Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) {\n\t// Load the jwt secret from the Gin config if the secret isn't specified.\n\tif secret == \"\" {\n\t\tsecret = viper.GetString(\"jwt_secret\")\n\t}\n\t// The token content.\n\ttoken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{\n\t\t\"id\":       c.ID,\n\t\t\"username\": c.Username,\n\t\t\"nbf\":      time.Now().Unix(),\n\t\t\"iat\":      time.Now().Unix(),\n\t})\n\t// Sign the token with the specified secret.\n\ttokenString, err = token.SignedString([]byte(secret))\n\n\treturn\n}\n"
  },
  {
    "path": "demo12/pkg/version/base.go",
    "content": "package version\n\nvar (\n\tgitTag       string = \"\"\n\tgitCommit    string = \"$Format:%H$\"          // sha1 from git, output of $(git rev-parse HEAD)\n\tgitTreeState string = \"not a git tree\"       // state of git tree, either \"clean\" or \"dirty\"\n\tbuildDate    string = \"1970-01-01T00:00:00Z\" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')\n)\n"
  },
  {
    "path": "demo12/pkg/version/doc.go",
    "content": "package version\n"
  },
  {
    "path": "demo12/pkg/version/version.go",
    "content": "package version\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n)\n\n// Info contains versioning information.\ntype Info struct {\n\tGitTag       string `json:\"gitTag\"`\n\tGitCommit    string `json:\"gitCommit\"`\n\tGitTreeState string `json:\"gitTreeState\"`\n\tBuildDate    string `json:\"buildDate\"`\n\tGoVersion    string `json:\"goVersion\"`\n\tCompiler     string `json:\"compiler\"`\n\tPlatform     string `json:\"platform\"`\n}\n\n// String returns info as a human-friendly version string.\nfunc (info Info) String() string {\n\treturn info.GitTag\n}\n\nfunc Get() Info {\n\treturn Info{\n\t\tGitTag:       gitTag,\n\t\tGitCommit:    gitCommit,\n\t\tGitTreeState: gitTreeState,\n\t\tBuildDate:    buildDate,\n\t\tGoVersion:    runtime.Version(),\n\t\tCompiler:     runtime.Compiler,\n\t\tPlatform:     fmt.Sprintf(\"%s/%s\", runtime.GOOS, runtime.GOARCH),\n\t}\n}\n"
  },
  {
    "path": "demo12/router/middleware/auth.go",
    "content": "package middleware\n\nimport (\n\t\"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/pkg/token\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc AuthMiddleware() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// Parse the json web token.\n\t\tif _, err := token.ParseRequest(c); err != nil {\n\t\t\thandler.SendResponse(c, errno.ErrTokenInvalid, nil)\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "demo12/router/middleware/header.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// NoCache is a middleware function that appends headers\n// to prevent the client from caching the HTTP response.\nfunc NoCache(c *gin.Context) {\n\tc.Header(\"Cache-Control\", \"no-cache, no-store, max-age=0, must-revalidate, value\")\n\tc.Header(\"Expires\", \"Thu, 01 Jan 1970 00:00:00 GMT\")\n\tc.Header(\"Last-Modified\", time.Now().UTC().Format(http.TimeFormat))\n\tc.Next()\n}\n\n// Options is a middleware function that appends headers\n// for options requests and aborts then exits the middleware\n// chain and ends the request.\nfunc Options(c *gin.Context) {\n\tif c.Request.Method != \"OPTIONS\" {\n\t\tc.Next()\n\t} else {\n\t\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\t\tc.Header(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Access-Control-Allow-Headers\", \"authorization, origin, content-type, accept\")\n\t\tc.Header(\"Allow\", \"HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Content-Type\", \"application/json\")\n\t\tc.AbortWithStatus(200)\n\t}\n}\n\n// Secure is a middleware function that appends security\n// and resource access headers.\nfunc Secure(c *gin.Context) {\n\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\tc.Header(\"X-Frame-Options\", \"DENY\")\n\tc.Header(\"X-Content-Type-Options\", \"nosniff\")\n\tc.Header(\"X-XSS-Protection\", \"1; mode=block\")\n\tif c.Request.TLS != nil {\n\t\tc.Header(\"Strict-Transport-Security\", \"max-age=31536000\")\n\t}\n\n\t// Also consider adding Content-Security-Policy headers\n\t// c.Header(\"Content-Security-Policy\", \"script-src 'self' https://cdnjs.cloudflare.com\")\n}\n"
  },
  {
    "path": "demo12/router/middleware/logging.go",
    "content": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/willf/pad\"\n)\n\ntype bodyLogWriter struct {\n\tgin.ResponseWriter\n\tbody *bytes.Buffer\n}\n\nfunc (w bodyLogWriter) Write(b []byte) (int, error) {\n\tw.body.Write(b)\n\treturn w.ResponseWriter.Write(b)\n}\n\n// Logging is a middleware function that logs the each request.\nfunc Logging() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tstart := time.Now().UTC()\n\t\tpath := c.Request.URL.Path\n\n\t\treg := regexp.MustCompile(\"(/v1/user|/login)\")\n\t\tif !reg.MatchString(path) {\n\t\t\treturn\n\t\t}\n\n\t\t// Skip for the health check requests.\n\t\tif path == \"/sd/health\" || path == \"/sd/ram\" || path == \"/sd/cpu\" || path == \"/sd/disk\" {\n\t\t\treturn\n\t\t}\n\n\t\t// Read the Body content\n\t\tvar bodyBytes []byte\n\t\tif c.Request.Body != nil {\n\t\t\tbodyBytes, _ = ioutil.ReadAll(c.Request.Body)\n\t\t}\n\n\t\t// Restore the io.ReadCloser to its original state\n\t\tc.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))\n\n\t\t// The basic informations.\n\t\tmethod := c.Request.Method\n\t\tip := c.ClientIP()\n\n\t\t//log.Debugf(\"New request come in, path: %s, Method: %s, body `%s`\", path, method, string(bodyBytes))\n\t\tblw := &bodyLogWriter{\n\t\t\tbody:           bytes.NewBufferString(\"\"),\n\t\t\tResponseWriter: c.Writer,\n\t\t}\n\t\tc.Writer = blw\n\n\t\t// Continue.\n\t\tc.Next()\n\n\t\t// Calculates the latency.\n\t\tend := time.Now().UTC()\n\t\tlatency := end.Sub(start)\n\n\t\tcode, message := -1, \"\"\n\n\t\t// get code and message\n\t\tvar response handler.Response\n\t\tif err := json.Unmarshal(blw.body.Bytes(), &response); err != nil {\n\t\t\tlog.Errorf(err, \"response body can not unmarshal to model.Response struct, body: `%s`\", blw.body.Bytes())\n\t\t\tcode = errno.InternalServerError.Code\n\t\t\tmessage = err.Error()\n\t\t} else {\n\t\t\tcode = response.Code\n\t\t\tmessage = response.Message\n\t\t}\n\n\t\tlog.Infof(\"%-13s | %-12s | %s %s | {code: %d, message: %s}\", latency, ip, pad.Right(method, 5, \"\"), path, code, message)\n\t}\n}\n"
  },
  {
    "path": "demo12/router/middleware/requestid.go",
    "content": "package middleware\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/satori/go.uuid\"\n)\n\nfunc RequestId() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// Check for incoming header, use it if exists\n\t\trequestId := c.Request.Header.Get(\"X-Request-Id\")\n\n\t\t// Create request id with UUID4\n\t\tif requestId == \"\" {\n\t\t\tu4, _ := uuid.NewV4()\n\t\t\trequestId = u4.String()\n\t\t}\n\n\t\t// Expose it for use in the application\n\t\tc.Set(\"X-Request-Id\", requestId)\n\n\t\t// Set X-Request-Id header\n\t\tc.Writer.Header().Set(\"X-Request-Id\", requestId)\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "demo12/router/router.go",
    "content": "package router\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/handler/sd\"\n\t\"apiserver/handler/user\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Load loads the middlewares, routes, handlers.\nfunc Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {\n\t// Middlewares.\n\tg.Use(gin.Recovery())\n\tg.Use(middleware.NoCache)\n\tg.Use(middleware.Options)\n\tg.Use(middleware.Secure)\n\tg.Use(mw...)\n\t// 404 Handler.\n\tg.NoRoute(func(c *gin.Context) {\n\t\tc.String(http.StatusNotFound, \"The incorrect API route.\")\n\t})\n\n\t// api for authentication functionalities\n\tg.POST(\"/login\", user.Login)\n\n\t// The user handlers, requiring authentication\n\tu := g.Group(\"/v1/user\")\n\tu.Use(middleware.AuthMiddleware())\n\t{\n\t\tu.POST(\"\", user.Create)\n\t\tu.DELETE(\"/:id\", user.Delete)\n\t\tu.PUT(\"/:id\", user.Update)\n\t\tu.GET(\"\", user.List)\n\t\tu.GET(\"/:username\", user.Get)\n\t}\n\n\t// The health check handlers\n\tsvcd := g.Group(\"/sd\")\n\t{\n\t\tsvcd.GET(\"/health\", sd.HealthCheck)\n\t\tsvcd.GET(\"/disk\", sd.DiskCheck)\n\t\tsvcd.GET(\"/cpu\", sd.CPUCheck)\n\t\tsvcd.GET(\"/ram\", sd.RAMCheck)\n\t}\n\n\treturn g\n}\n"
  },
  {
    "path": "demo12/service/service.go",
    "content": "package service\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\n\t\"apiserver/model\"\n\t\"apiserver/util\"\n)\n\nfunc ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) {\n\tinfos := make([]*model.UserInfo, 0)\n\tusers, count, err := model.ListUser(username, offset, limit)\n\tif err != nil {\n\t\treturn nil, count, err\n\t}\n\n\tids := []uint64{}\n\tfor _, user := range users {\n\t\tids = append(ids, user.Id)\n\t}\n\n\twg := sync.WaitGroup{}\n\tuserList := model.UserList{\n\t\tLock:  new(sync.Mutex),\n\t\tIdMap: make(map[uint64]*model.UserInfo, len(users)),\n\t}\n\n\terrChan := make(chan error, 1)\n\tfinished := make(chan bool, 1)\n\n\t// Improve query efficiency in parallel\n\tfor _, u := range users {\n\t\twg.Add(1)\n\t\tgo func(u *model.UserModel) {\n\t\t\tdefer wg.Done()\n\n\t\t\tshortId, err := util.GenShortId()\n\t\t\tif err != nil {\n\t\t\t\terrChan <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tuserList.Lock.Lock()\n\t\t\tdefer userList.Lock.Unlock()\n\t\t\tuserList.IdMap[u.Id] = &model.UserInfo{\n\t\t\t\tId:        u.Id,\n\t\t\t\tUsername:  u.Username,\n\t\t\t\tSayHello:  fmt.Sprintf(\"Hello %s\", shortId),\n\t\t\t\tPassword:  u.Password,\n\t\t\t\tCreatedAt: u.CreatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t\tUpdatedAt: u.UpdatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t}\n\t\t}(u)\n\t}\n\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(finished)\n\t}()\n\n\tselect {\n\tcase <-finished:\n\tcase err := <-errChan:\n\t\treturn nil, count, err\n\t}\n\n\tfor _, id := range ids {\n\t\tinfos = append(infos, userList.IdMap[id])\n\t}\n\n\treturn infos, count, nil\n}\n"
  },
  {
    "path": "demo12/util/util.go",
    "content": "package util\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/teris-io/shortid\"\n)\n\nfunc GenShortId() (string, error) {\n\treturn shortid.Generate()\n}\n\nfunc GetReqID(c *gin.Context) string {\n\tv, ok := c.Get(\"X-Request-Id\")\n\tif !ok {\n\t\treturn \"\"\n\t}\n\tif requestId, ok := v.(string); ok {\n\t\treturn requestId\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "demo13/Makefile",
    "content": "SHELL := /bin/bash\nBASEDIR = $(shell pwd)\n\n# build with verison infos\nversionDir = \"apiserver/pkg/version\"\ngitTag = $(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)\nbuildDate = $(shell TZ=Asia/Shanghai date +%FT%T%z)\ngitCommit = $(shell git log --pretty=format:'%H' -n 1)\ngitTreeState = $(shell if git status|grep -q 'clean';then echo clean; else echo dirty; fi)\n\nldflags=\"-w -X ${versionDir}.gitTag=${gitTag} -X ${versionDir}.buildDate=${buildDate} -X ${versionDir}.gitCommit=${gitCommit} -X ${versionDir}.gitTreeState=${gitTreeState}\"\n\nall: gotool\n\t@go build -v -ldflags ${ldflags} .\nclean:\n\trm -f apiserver\n\tfind . -name \"[._]*.s[a-w][a-z]\" | xargs -i rm -f {}\ngotool:\n\tgofmt -w .\n\tgo tool vet . |& grep -v vendor;true\nca:\n\topenssl 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\"\n\nhelp:\n\t@echo \"make - compile the source code\"\n\t@echo \"make clean - remove binary file and vim swp files\"\n\t@echo \"make gotool - run go tool 'fmt' and 'vet'\"\n\t@echo \"make ca - generate ca files\"\n\n.PHONY: clean gotool ca help\n\n\n"
  },
  {
    "path": "demo13/README.md",
    "content": "进阶：给API增加启动脚本\n"
  },
  {
    "path": "demo13/admin.sh",
    "content": "#!/bin/bash\n\nSERVER=\"apiserver\"\nBASE_DIR=$PWD\nINTERVAL=2\n\n# 命令行参数，需要手动指定\nARGS=\"\"\n\nfunction start()\n{\n\tif [ \"`pgrep $SERVER -u $UID`\" != \"\" ];then\n\t\techo \"$SERVER already running\"\n\t\texit 1\n\tfi\n\n\tnohup $BASE_DIR/$SERVER $ARGS  server &>/dev/null &\n\n\techo \"sleeping...\" &&  sleep $INTERVAL\n\n\t# check status\n\tif [ \"`pgrep $SERVER -u $UID`\" == \"\" ];then\n\t\techo \"$SERVER start failed\"\n\t\texit 1\n\tfi\n}\n\nfunction status() \n{\n\tif [ \"`pgrep $SERVER -u $UID`\" != \"\" ];then\n\t\techo $SERVER is running\n\telse\n\t\techo $SERVER is not running\n\tfi\n}\n\nfunction stop() \n{\n\tif [ \"`pgrep $SERVER -u $UID`\" != \"\" ];then\n\t\tkill -9 `pgrep $SERVER -u $UID`\n\tfi\n\n\techo \"sleeping...\" &&  sleep $INTERVAL\n\n\tif [ \"`pgrep $SERVER -u $UID`\" != \"\" ];then\n\t\techo \"$SERVER stop failed\"\n\t\texit 1\n\tfi\n}\n\ncase \"$1\" in\n\t'start')\n\tstart\n\t;;  \n\t'stop')\n\tstop\n\t;;  \n\t'status')\n\tstatus\n\t;;  \n\t'restart')\n\tstop && start\n\t;;  \n\t*)  \n\techo \"usage: $0 {start|stop|restart|status}\"\n\texit 1\n\t;;  \nesac\n"
  },
  {
    "path": "demo13/conf/config.yaml",
    "content": "runmode: debug                 # 开发模式, debug, release, test\naddr: :8080                  # HTTP绑定端口\nname: apiserver              # API Server的名字\nurl: http://127.0.0.1:8080   # pingServer函数请求的API服务器的ip:port\nmax_ping_count: 10           # pingServer函数try的次数\njwt_secret: Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5\ntls:\n  addr: :8081\n  cert: conf/server.crt\n  key: conf/server.key\nlog:\n  writers: stdout\n  logger_level: DEBUG\n  logger_file: log/apiserver.log\n  log_format_text: true\n  rollingPolicy: size\n  log_rotate_date: 1\n  log_rotate_size: 1\n  log_backup_count: 7\ndb:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\ndocker_db:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\n"
  },
  {
    "path": "demo13/conf/server.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIID2TCCAsGgAwIBAgIJAI/8axTNy37vMA0GCSqGSIb3DQEBBQUAMIGCMQswCQYD\nVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO\nUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAkxMjcuMC4wLjEx\nGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNvbTAeFw0xODA2MDQwNjMwMzFaFw0y\nODA2MDEwNjMwMzFaMIGCMQswCQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYD\nVQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklU\nMRIwEAYDVQQDDAkxMjcuMC4wLjExGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNv\nbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKHbnpmaxHokFC8rnJlG\n42eL4C3tog7rffv96DrwDajdFNk8DeaNUpZ+R4X4YPecumsCsbol2dDC1IHHGJUN\nq/A1vRjyDptMLmzp30p1aqVPcz+j8GMXtZUflLgup+HeKZRdrsk/0SaH1C3ywUBm\nyDMNHUxEvoFj0x9aJoPutnkEk9miZt/kF2vxlTbKFM14/TecVr3ljNwTebJAqEw9\niaZpNXn/IQV2I2PTGlxjyT6XNsCchjMTJ9y/XS9rBeX6BFHgQsV52CW1mWz8FieM\n/nyF9hE/TZKCpMtHWEYIdBbrxxY0BlhMxEtyIFzTha5zltFrQJGUYz7RIlyuabjk\nor8CAwEAAaNQME4wHQYDVR0OBBYEFIjLBQlToqUDoyOLeUd6HeaqiiLJMB8GA1Ud\nIwQYMBaAFIjLBQlToqUDoyOLeUd6HeaqiiLJMAwGA1UdEwQFMAMBAf8wDQYJKoZI\nhvcNAQEFBQADggEBAHAqpqGNUNgYCr3JvrVULDqFKmqLpvDKfLd6kmtnFzvuZLSz\nANE2RXXqQ5Ms3F0oHqZQq4aOvgvfas5dBLoToPL/ip+GibQ7O/1PsPD5bZaczUel\nAnmClX4ewcOEWmZx9mbYOz2C8RREvKU3MoBERrWA33LquYH0+8W8WHyMKNNF97oD\nBgWZ8R1t3N59ySZyHcye7ddlJUubRbdyCGrHbmziHV3Cj61NJiwS2JRvhYkc71M6\n8921j63qEM6i0NusFYG/w0GM3x/OeaeY/om6x+IOhJp3vIa1JPSekVyNSCwDEc3B\n+8WmVFaeMMXwErSkuu8nCbqRP8J1jiKgzpG/92g=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "demo13/conf/server.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCh256ZmsR6JBQv\nK5yZRuNni+At7aIO6337/eg68A2o3RTZPA3mjVKWfkeF+GD3nLprArG6JdnQwtSB\nxxiVDavwNb0Y8g6bTC5s6d9KdWqlT3M/o/BjF7WVH5S4Lqfh3imUXa7JP9Emh9Qt\n8sFAZsgzDR1MRL6BY9MfWiaD7rZ5BJPZombf5Bdr8ZU2yhTNeP03nFa95YzcE3my\nQKhMPYmmaTV5/yEFdiNj0xpcY8k+lzbAnIYzEyfcv10vawXl+gRR4ELFedgltZls\n/BYnjP58hfYRP02SgqTLR1hGCHQW68cWNAZYTMRLciBc04Wuc5bRa0CRlGM+0SJc\nrmm45KK/AgMBAAECggEASNMWvgf7pPT8u+iEchaKFLnDqQaFZu8f5TRtu67shnDK\ng59YpcYqRZoVtjp17pLu8Vzp+FY1dY9jq+yXq+DV3qNfLI0kc01IiiqEE+1WiYCA\n2z541y0Av1LRSDl9wcuCq8Wm8der1AlDN1VFDCPyqb2Z1AoOKQtwH2ghcjUCltn4\nNxuMYOVdql+Idl/7PCEha9pW3DQvPqJVXRPvBh6tB7FFuUOIq4+EFRUWWZlyCJfQ\nfYxB9jcFUWmAUieV3iBajg6H6QVkj/STLSgKuSktEBM2mqyL4slHXFWRt7+NQikg\ntOEWnqcTC4Ei2Dyam4SBXHJ1wT8AJV4kMTrN1ZBRmQKBgQDOMcIGHyuBI1aHcvPW\nTkein7bslFAKsA1LBqnw+alWzSHMKEmfWbBjeW0FQTIGEYMXwIq5JhnNcS5Kb5Qt\nBZgbBPDb7S80DtJnkno3iJ6hpijE3X/29lezigIuGibO5MD0Na1am2BP0iU77ifV\nxpYvj/j/pkdcTLM7hRDImSeNIwKBgQDI9EUFkGi6H9NZEles+MCHenVRYUBT2PXe\nhmtd9d5f4v3CmRsXvl4Dl/slIrJ9kuZktAck3Ocx6D0hnZcj1mwq077K7H3QqKCl\nzRwB11H5v/imsZ1ey0hLVPhCzr2miRPM/9oL5B2st9eotzhyRcrMn+oYZSSt79S8\nQ3YDiZ7TtQKBgENSr7z79GJnvVrgR4kTagRJDZrVGgVDUjPK6zXI7mdu9rgH93HW\nAOeZv+TVUpX0pc7diO3G6OnRKIIZSFIi33UC+fl0ydK/fCdhBhKXwuOYsvsEL0Hd\nUOlICEoxM7adrfqOhBlvXdTyEkItEkiUXHkPEwe1rNsQF/05By/YAbftAoGAYB2F\njd2+WZezTN0bFl58J9CIoH31eKVDJEYCwJRC4nX9jcARV0/0Q5/DvcVUvf8vN2ds\nK1OFOTetVZC8o6WBYxKYJRLsMosVG3h5NuA4E06grYoyjQ6J644emEWuLCNQVzLg\npeNb1iqweb/4vZ9oGms6WqS14IPfqpRRs+t1DikCgYEAyFtQKlLmNm/EirOMqjS2\n4/zMRj4B6n8BB8ZJsVC/MkuD+4B03rY44pP324cJnqVIcug9Zoijffnh1qbtZEc/\nMhNCa4a2MY0qcd4r/43cGbjqv94En7e5TgkH/79W+lbR3uBayt33RuuKStauZkf/\n2eBi4LlGO5stoeD63h9Ten0=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "demo13/config/config.go",
    "content": "package config\n\nimport (\n\t\"strings\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n)\n\ntype Config struct {\n\tName string\n}\n\nfunc Init(cfg string) error {\n\tc := Config{\n\t\tName: cfg,\n\t}\n\n\t// 初始化配置文件\n\tif err := c.initConfig(); err != nil {\n\t\treturn err\n\t}\n\n\t// 初始化日志包\n\tc.initLog()\n\n\t// 监控配置文件变化并热加载程序\n\tc.watchConfig()\n\n\treturn nil\n}\n\nfunc (c *Config) initConfig() error {\n\tif c.Name != \"\" {\n\t\tviper.SetConfigFile(c.Name) // 如果指定了配置文件，则解析指定的配置文件\n\t} else {\n\t\tviper.AddConfigPath(\"conf\") // 如果没有指定配置文件，则解析默认的配置文件\n\t\tviper.SetConfigName(\"config\")\n\t}\n\tviper.SetConfigType(\"yaml\")     // 设置配置文件格式为YAML\n\tviper.AutomaticEnv()            // 读取匹配的环境变量\n\tviper.SetEnvPrefix(\"APISERVER\") // 读取环境变量的前缀为APISERVER\n\treplacer := strings.NewReplacer(\".\", \"_\")\n\tviper.SetEnvKeyReplacer(replacer)\n\tif err := viper.ReadInConfig(); err != nil { // viper解析配置文件\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *Config) initLog() {\n\tpassLagerCfg := log.PassLagerCfg{\n\t\tWriters:        viper.GetString(\"log.writers\"),\n\t\tLoggerLevel:    viper.GetString(\"log.logger_level\"),\n\t\tLoggerFile:     viper.GetString(\"log.logger_file\"),\n\t\tLogFormatText:  viper.GetBool(\"log.log_format_text\"),\n\t\tRollingPolicy:  viper.GetString(\"log.rollingPolicy\"),\n\t\tLogRotateDate:  viper.GetInt(\"log.log_rotate_date\"),\n\t\tLogRotateSize:  viper.GetInt(\"log.log_rotate_size\"),\n\t\tLogBackupCount: viper.GetInt(\"log.log_backup_count\"),\n\t}\n\n\tlog.InitWithConfig(&passLagerCfg)\n}\n\n// 监控配置文件变化并热加载程序\nfunc (c *Config) watchConfig() {\n\tviper.WatchConfig()\n\tviper.OnConfigChange(func(e fsnotify.Event) {\n\t\tlog.Infof(\"Config file changed: %s\", e.Name)\n\t})\n}\n"
  },
  {
    "path": "demo13/db.sql",
    "content": "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n/*!40101 SET NAMES utf8 */;\n/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;\n/*!40103 SET TIME_ZONE='+00:00' */;\n/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;\n/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n\nCREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */;\n\nUSE `db_apiserver`;\n\n--\n-- Table structure for table `tb_users`\n--\n\nDROP TABLE IF EXISTS `tb_users`;\n/*!40101 SET @saved_cs_client     = @@character_set_client */;\n/*!40101 SET character_set_client = utf8 */;\nCREATE TABLE `tb_users` (\n  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n  `username` varchar(255) NOT NULL,\n  `password` varchar(255) NOT NULL,\n  `createdAt` timestamp NULL DEFAULT NULL,\n  `updatedAt` timestamp NULL DEFAULT NULL,\n  `deletedAt` timestamp NULL DEFAULT NULL,\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `username` (`username`),\n  KEY `idx_tb_users_deletedAt` (`deletedAt`)\n) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;\n/*!40101 SET character_set_client = @saved_cs_client */;\n\n--\n-- Dumping data for table `tb_users`\n--\n\nLOCK TABLES `tb_users` WRITE;\n/*!40000 ALTER TABLE `tb_users` DISABLE KEYS */;\nINSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL);\n/*!40000 ALTER TABLE `tb_users` ENABLE KEYS */;\nUNLOCK TABLES;\n/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;\n\n/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;\n/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;\n/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n\n-- Dump completed on 2018-05-28  0:25:41\n"
  },
  {
    "path": "demo13/handler/handler.go",
    "content": "package handler\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype Response struct {\n\tCode    int         `json:\"code\"`\n\tMessage string      `json:\"message\"`\n\tData    interface{} `json:\"data\"`\n}\n\nfunc SendResponse(c *gin.Context, err error, data interface{}) {\n\tcode, message := errno.DecodeErr(err)\n\n\t// always return http.StatusOK\n\tc.JSON(http.StatusOK, Response{\n\t\tCode:    code,\n\t\tMessage: message,\n\t\tData:    data,\n\t})\n}\n"
  },
  {
    "path": "demo13/handler/sd/check.go",
    "content": "package sd\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/shirou/gopsutil/cpu\"\n\t\"github.com/shirou/gopsutil/disk\"\n\t\"github.com/shirou/gopsutil/load\"\n\t\"github.com/shirou/gopsutil/mem\"\n)\n\nconst (\n\tB  = 1\n\tKB = 1024 * B\n\tMB = 1024 * KB\n\tGB = 1024 * MB\n)\n\n// HealthCheck shows `OK` as the ping-pong result.\nfunc HealthCheck(c *gin.Context) {\n\tmessage := \"OK\"\n\tc.String(http.StatusOK, \"\\n\"+message)\n}\n\n// DiskCheck checks the disk usage.\nfunc DiskCheck(c *gin.Context) {\n\tu, _ := disk.Usage(\"/\")\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusOK\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n\n// CPUCheck checks the cpu usage.\nfunc CPUCheck(c *gin.Context) {\n\tcores, _ := cpu.Counts(false)\n\n\ta, _ := load.Avg()\n\tl1 := a.Load1\n\tl5 := a.Load5\n\tl15 := a.Load15\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif l5 >= float64(cores-1) {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if l5 >= float64(cores-2) {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Load average: %.2f, %.2f, %.2f | Cores: %d\", text, l1, l5, l15, cores)\n\tc.String(status, \"\\n\"+message)\n}\n\n// RAMCheck checks the disk usage.\nfunc RAMCheck(c *gin.Context) {\n\tu, _ := mem.VirtualMemory()\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n"
  },
  {
    "path": "demo13/handler/user/create.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// Create creates a new user account.\nfunc Create(c *gin.Context) {\n\tlog.Info(\"User Create function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\tvar r CreateRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tu := model.UserModel{\n\t\tUsername: r.Username,\n\t\tPassword: r.Password,\n\t}\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\t// Insert the user to the database.\n\tif err := u.Create(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\trsp := CreateResponse{\n\t\tUsername: r.Username,\n\t}\n\n\t// Show the user information.\n\tSendResponse(c, nil, rsp)\n}\n"
  },
  {
    "path": "demo13/handler/user/delete.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Delete delete an user by the user identifier.\nfunc Delete(c *gin.Context) {\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\tif err := model.DeleteUser(uint64(userId)); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo13/handler/user/get.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Get gets an user by the user identifier.\nfunc Get(c *gin.Context) {\n\tusername := c.Param(\"username\")\n\t// Get the user by the `username` from the database.\n\tuser, err := model.GetUser(username)\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrUserNotFound, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, user)\n}\n"
  },
  {
    "path": "demo13/handler/user/list.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/service\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// List list the users in the database.\nfunc List(c *gin.Context) {\n\tvar r ListRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tinfos, count, err := service.ListUser(r.Username, r.Offset, r.Limit)\n\tif err != nil {\n\t\tSendResponse(c, err, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, ListResponse{\n\t\tTotalCount: count,\n\t\tUserList:   infos,\n\t})\n}\n"
  },
  {
    "path": "demo13/handler/user/login.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/auth\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/pkg/token\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Login generates the authentication token\n// if the password was matched with the specified account.\nfunc Login(c *gin.Context) {\n\t// Binding the data with the user struct.\n\tvar u model.UserModel\n\tif err := c.Bind(&u); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\t// Get the user information by the login username.\n\td, err := model.GetUser(u.Username)\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrUserNotFound, nil)\n\t\treturn\n\t}\n\n\t// Compare the login password with the user password.\n\tif err := auth.Compare(d.Password, u.Password); err != nil {\n\t\tSendResponse(c, errno.ErrPasswordIncorrect, nil)\n\t\treturn\n\t}\n\n\t// Sign the json web token.\n\tt, err := token.Sign(c, token.Context{ID: d.Id, Username: d.Username}, \"\")\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrToken, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, model.Token{Token: t})\n}\n"
  },
  {
    "path": "demo13/handler/user/update.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// Update update a exist user account info.\nfunc Update(c *gin.Context) {\n\tlog.Info(\"Update function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\t// Get the user id from the url parameter.\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\n\t// Binding the user data.\n\tvar u model.UserModel\n\tif err := c.Bind(&u); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\t// We update the record based on the user id.\n\tu.Id = uint64(userId)\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\n\t// Save changed fields.\n\tif err := u.Update(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo13/handler/user/user.go",
    "content": "package user\n\nimport (\n\t\"apiserver/model\"\n)\n\ntype CreateRequest struct {\n\tUsername string `json:\"username\"`\n\tPassword string `json:\"password\"`\n}\n\ntype CreateResponse struct {\n\tUsername string `json:\"username\"`\n}\n\ntype ListRequest struct {\n\tUsername string `json:\"username\"`\n\tOffset   int    `json:\"offset\"`\n\tLimit    int    `json:\"limit\"`\n}\n\ntype ListResponse struct {\n\tTotalCount uint64            `json:\"totalCount\"`\n\tUserList   []*model.UserInfo `json:\"userList\"`\n}\n"
  },
  {
    "path": "demo13/main.go",
    "content": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n\n\t\"apiserver/config\"\n\t\"apiserver/model\"\n\tv \"apiserver/pkg/version\"\n\t\"apiserver/router\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tcfg     = pflag.StringP(\"config\", \"c\", \"\", \"apiserver config file path.\")\n\tversion = pflag.BoolP(\"version\", \"v\", false, \"show version info.\")\n)\n\nfunc main() {\n\tpflag.Parse()\n\tif *version {\n\t\tv := v.Get()\n\t\tmarshalled, err := json.MarshalIndent(&v, \"\", \"  \")\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"%v\\n\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tfmt.Println(string(marshalled))\n\t\treturn\n\t}\n\n\t// init config\n\tif err := config.Init(*cfg); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// init db\n\tmodel.DB.Init()\n\tdefer model.DB.Close()\n\n\t// Set gin mode.\n\tgin.SetMode(viper.GetString(\"runmode\"))\n\n\t// Create the Gin engine.\n\tg := gin.New()\n\n\t// Routes.\n\trouter.Load(\n\t\t// Cores.\n\t\tg,\n\n\t\t// Middlwares.\n\t\tmiddleware.Logging(),\n\t\tmiddleware.RequestId(),\n\t)\n\n\t// Ping the server to make sure the router is working.\n\tgo func() {\n\t\tif err := pingServer(); err != nil {\n\t\t\tlog.Fatal(\"The router has no response, or it might took too long to start up.\", err)\n\t\t}\n\t\tlog.Info(\"The router has been deployed successfully.\")\n\t}()\n\n\t// Start to listening the incoming requests.\n\tcert := viper.GetString(\"tls.cert\")\n\tkey := viper.GetString(\"tls.key\")\n\tif cert != \"\" && key != \"\" {\n\t\tgo func() {\n\t\t\tlog.Infof(\"Start to listening the incoming requests on https address: %s\", viper.GetString(\"tls.addr\"))\n\t\t\tlog.Info(http.ListenAndServeTLS(viper.GetString(\"tls.addr\"), cert, key, g).Error())\n\t\t}()\n\t}\n\n\tlog.Infof(\"Start to listening the incoming requests on http address: %s\", viper.GetString(\"addr\"))\n\tlog.Info(http.ListenAndServe(viper.GetString(\"addr\"), g).Error())\n}\n\n// pingServer pings the http server to make sure the router is working.\nfunc pingServer() error {\n\tfor i := 0; i < viper.GetInt(\"max_ping_count\"); i++ {\n\t\t// Ping the server by sending a GET request to `/health`.\n\t\tresp, err := http.Get(viper.GetString(\"url\") + \"/sd/health\")\n\t\tif err == nil && resp.StatusCode == 200 {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Sleep for a second to continue the next ping.\n\t\tlog.Info(\"Waiting for the router, retry in 1 second.\")\n\t\ttime.Sleep(time.Second)\n\t}\n\treturn errors.New(\"Cannot connect to the router.\")\n}\n"
  },
  {
    "path": "demo13/model/init.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n\t// MySQL driver.\n\t\"github.com/jinzhu/gorm\"\n\t_ \"github.com/jinzhu/gorm/dialects/mysql\"\n)\n\ntype Database struct {\n\tSelf   *gorm.DB\n\tDocker *gorm.DB\n}\n\nvar DB *Database\n\nfunc openDB(username, password, addr, name string) *gorm.DB {\n\tconfig := fmt.Sprintf(\"%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s\",\n\t\tusername,\n\t\tpassword,\n\t\taddr,\n\t\tname,\n\t\ttrue,\n\t\t//\"Asia/Shanghai\"),\n\t\t\"Local\")\n\n\tdb, err := gorm.Open(\"mysql\", config)\n\tif err != nil {\n\t\tlog.Errorf(err, \"Database connection failed. Database name: %s\", name)\n\t}\n\n\t// set for db connection\n\tsetupDB(db)\n\n\treturn db\n}\n\nfunc setupDB(db *gorm.DB) {\n\tdb.LogMode(viper.GetBool(\"gormlog\"))\n\t//db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数，默认值为0表示不限制.设置最大的连接数，可以避免并发太高导致连接mysql出现too many connections的错误。\n\tdb.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。\n}\n\n// used for cli\nfunc InitSelfDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"db.username\"),\n\t\tviper.GetString(\"db.password\"),\n\t\tviper.GetString(\"db.addr\"),\n\t\tviper.GetString(\"db.name\"))\n}\n\nfunc GetSelfDB() *gorm.DB {\n\treturn InitSelfDB()\n}\n\nfunc InitDockerDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"docker_db.username\"),\n\t\tviper.GetString(\"docker_db.password\"),\n\t\tviper.GetString(\"docker_db.addr\"),\n\t\tviper.GetString(\"docker_db.name\"))\n}\n\nfunc GetDockerDB() *gorm.DB {\n\treturn InitDockerDB()\n}\n\nfunc (db *Database) Init() {\n\tDB = &Database{\n\t\tSelf:   GetSelfDB(),\n\t\tDocker: GetDockerDB(),\n\t}\n}\n\nfunc (db *Database) Close() {\n\tDB.Self.Close()\n\tDB.Docker.Close()\n}\n"
  },
  {
    "path": "demo13/model/model.go",
    "content": "package model\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype BaseModel struct {\n\tId        uint64     `gorm:\"primary_key;AUTO_INCREMENT;column:id\" json:\"-\"`\n\tCreatedAt time.Time  `gorm:\"column:createdAt\" json:\"-\"`\n\tUpdatedAt time.Time  `gorm:\"column:updatedAt\" json:\"-\"`\n\tDeletedAt *time.Time `gorm:\"column:deletedAt\" sql:\"index\" json:\"-\"`\n}\n\ntype UserInfo struct {\n\tId        uint64 `json:\"id\"`\n\tUsername  string `json:\"username\"`\n\tSayHello  string `json:\"sayHello\"`\n\tPassword  string `json:\"password\"`\n\tCreatedAt string `json:\"createdAt\"`\n\tUpdatedAt string `json:\"updatedAt\"`\n}\n\ntype UserList struct {\n\tLock  *sync.Mutex\n\tIdMap map[uint64]*UserInfo\n}\n\n// Token represents a JSON web token.\ntype Token struct {\n\tToken string `json:\"token\"`\n}\n"
  },
  {
    "path": "demo13/model/user.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"apiserver/pkg/auth\"\n\t\"apiserver/pkg/constvar\"\n\n\tvalidator \"gopkg.in/go-playground/validator.v9\"\n)\n\n// User represents a registered user.\ntype UserModel struct {\n\tBaseModel\n\tUsername string `json:\"username\" gorm:\"column:username;not null\" binding:\"required\" validate:\"min=1,max=32\"`\n\tPassword string `json:\"password\" gorm:\"column:password;not null\" binding:\"required\" validate:\"min=5,max=128\"`\n}\n\nfunc (c *UserModel) TableName() string {\n\treturn \"tb_users\"\n}\n\n// Create creates a new user account.\nfunc (u *UserModel) Create() error {\n\treturn DB.Self.Create(&u).Error\n}\n\n// DeleteUser deletes the user by the user identifier.\nfunc DeleteUser(id uint64) error {\n\tuser := UserModel{}\n\tuser.BaseModel.Id = id\n\treturn DB.Self.Delete(&user).Error\n}\n\n// Update updates an user account information.\nfunc (u *UserModel) Update() error {\n\treturn DB.Self.Save(u).Error\n}\n\n// GetUser gets an user by the user identifier.\nfunc GetUser(username string) (*UserModel, error) {\n\tu := &UserModel{}\n\td := DB.Self.Where(\"username = ?\", username).First(&u)\n\treturn u, d.Error\n}\n\n// ListUser List all users\nfunc ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) {\n\tif limit == 0 {\n\t\tlimit = constvar.DefaultLimit\n\t}\n\n\tusers := make([]*UserModel, 0)\n\tvar count uint64\n\n\twhere := fmt.Sprintf(\"username like '%%%s%%'\", username)\n\tif err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\tif err := DB.Self.Where(where).Offset(offset).Limit(limit).Order(\"id desc\").Find(&users).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\treturn users, count, nil\n}\n\n// Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct).\nfunc (u *UserModel) Compare(pwd string) (err error) {\n\terr = auth.Compare(u.Password, pwd)\n\treturn\n}\n\n// Encrypt the user password.\nfunc (u *UserModel) Encrypt() (err error) {\n\tu.Password, err = auth.Encrypt(u.Password)\n\treturn\n}\n\n// Validate the fields.\nfunc (u *UserModel) Validate() error {\n\tvalidate := validator.New()\n\treturn validate.Struct(u)\n}\n"
  },
  {
    "path": "demo13/pkg/auth/auth.go",
    "content": "package auth\n\nimport \"golang.org/x/crypto/bcrypt\"\n\n// Encrypt encrypts the plain text with bcrypt.\nfunc Encrypt(source string) (string, error) {\n\thashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost)\n\treturn string(hashedBytes), err\n}\n\n// Compare compares the encrypted text with the plain text if it's the same.\nfunc Compare(hashedPassword, password string) error {\n\treturn bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))\n}\n"
  },
  {
    "path": "demo13/pkg/constvar/constvar.go",
    "content": "package constvar\n\nconst (\n\tDefaultLimit = 50\n)\n"
  },
  {
    "path": "demo13/pkg/errno/code.go",
    "content": "package errno\n\nvar (\n\t// Common errors\n\tOK                  = &Errno{Code: 0, Message: \"OK\"}\n\tInternalServerError = &Errno{Code: 10001, Message: \"Internal server error\"}\n\tErrBind             = &Errno{Code: 10002, Message: \"Error occurred while binding the request body to the struct.\"}\n\n\tErrValidation = &Errno{Code: 20001, Message: \"Validation failed.\"}\n\tErrDatabase   = &Errno{Code: 20002, Message: \"Database error.\"}\n\tErrToken      = &Errno{Code: 20003, Message: \"Error occurred while signing the JSON web token.\"}\n\n\t// user errors\n\tErrEncrypt           = &Errno{Code: 20101, Message: \"Error occurred while encrypting the user password.\"}\n\tErrUserNotFound      = &Errno{Code: 20102, Message: \"The user was not found.\"}\n\tErrTokenInvalid      = &Errno{Code: 20103, Message: \"The token was invalid.\"}\n\tErrPasswordIncorrect = &Errno{Code: 20104, Message: \"The password was incorrect.\"}\n)\n"
  },
  {
    "path": "demo13/pkg/errno/errno.go",
    "content": "package errno\n\nimport \"fmt\"\n\ntype Errno struct {\n\tCode    int\n\tMessage string\n}\n\nfunc (err Errno) Error() string {\n\treturn err.Message\n}\n\n// Err represents an error\ntype Err struct {\n\tCode    int\n\tMessage string\n\tErr     error\n}\n\nfunc New(errno *Errno, err error) *Err {\n\treturn &Err{Code: errno.Code, Message: errno.Message, Err: err}\n}\n\nfunc (err *Err) Add(message string) error {\n\terr.Message += \" \" + message\n\treturn err\n}\n\nfunc (err *Err) Addf(format string, args ...interface{}) error {\n\terr.Message += \" \" + fmt.Sprintf(format, args...)\n\treturn err\n}\n\nfunc (err *Err) Error() string {\n\treturn fmt.Sprintf(\"Err - code: %d, message: %s, error: %s\", err.Code, err.Message, err.Err)\n}\n\nfunc IsErrUserNotFound(err error) bool {\n\tcode, _ := DecodeErr(err)\n\treturn code == ErrUserNotFound.Code\n}\n\nfunc DecodeErr(err error) (int, string) {\n\tif err == nil {\n\t\treturn OK.Code, OK.Message\n\t}\n\n\tswitch typed := err.(type) {\n\tcase *Err:\n\t\treturn typed.Code, typed.Message\n\tcase *Errno:\n\t\treturn typed.Code, typed.Message\n\tdefault:\n\t}\n\n\treturn InternalServerError.Code, err.Error()\n}\n"
  },
  {
    "path": "demo13/pkg/token/token.go",
    "content": "package token\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\tjwt \"github.com/dgrijalva/jwt-go\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\t// ErrMissingHeader means the `Authorization` header was empty.\n\tErrMissingHeader = errors.New(\"The length of the `Authorization` header is zero.\")\n)\n\n// Context is the context of the JSON web token.\ntype Context struct {\n\tID       uint64\n\tUsername string\n}\n\n// secretFunc validates the secret format.\nfunc secretFunc(secret string) jwt.Keyfunc {\n\treturn func(token *jwt.Token) (interface{}, error) {\n\t\t// Make sure the `alg` is what we except.\n\t\tif _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {\n\t\t\treturn nil, jwt.ErrSignatureInvalid\n\t\t}\n\n\t\treturn []byte(secret), nil\n\t}\n}\n\n// Parse validates the token with the specified secret,\n// and returns the context if the token was valid.\nfunc Parse(tokenString string, secret string) (*Context, error) {\n\tctx := &Context{}\n\n\t// Parse the token.\n\ttoken, err := jwt.Parse(tokenString, secretFunc(secret))\n\n\t// Parse error.\n\tif err != nil {\n\t\treturn ctx, err\n\n\t\t// Read the token if it's valid.\n\t} else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {\n\t\tctx.ID = uint64(claims[\"id\"].(float64))\n\t\tctx.Username = claims[\"username\"].(string)\n\t\treturn ctx, nil\n\n\t\t// Other errors.\n\t} else {\n\t\treturn ctx, err\n\t}\n}\n\n// ParseRequest gets the token from the header and\n// pass it to the Parse function to parses the token.\nfunc ParseRequest(c *gin.Context) (*Context, error) {\n\theader := c.Request.Header.Get(\"Authorization\")\n\n\t// Load the jwt secret from config\n\tsecret := viper.GetString(\"jwt_secret\")\n\n\tif len(header) == 0 {\n\t\treturn &Context{}, ErrMissingHeader\n\t}\n\n\tvar t string\n\t// Parse the header to get the token part.\n\tfmt.Sscanf(header, \"Bearer %s\", &t)\n\treturn Parse(t, secret)\n}\n\n// Sign signs the context with the specified secret.\nfunc Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) {\n\t// Load the jwt secret from the Gin config if the secret isn't specified.\n\tif secret == \"\" {\n\t\tsecret = viper.GetString(\"jwt_secret\")\n\t}\n\t// The token content.\n\ttoken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{\n\t\t\"id\":       c.ID,\n\t\t\"username\": c.Username,\n\t\t\"nbf\":      time.Now().Unix(),\n\t\t\"iat\":      time.Now().Unix(),\n\t})\n\t// Sign the token with the specified secret.\n\ttokenString, err = token.SignedString([]byte(secret))\n\n\treturn\n}\n"
  },
  {
    "path": "demo13/pkg/version/base.go",
    "content": "package version\n\nvar (\n\tgitTag       string = \"\"\n\tgitCommit    string = \"$Format:%H$\"          // sha1 from git, output of $(git rev-parse HEAD)\n\tgitTreeState string = \"not a git tree\"       // state of git tree, either \"clean\" or \"dirty\"\n\tbuildDate    string = \"1970-01-01T00:00:00Z\" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')\n)\n"
  },
  {
    "path": "demo13/pkg/version/doc.go",
    "content": "package version\n"
  },
  {
    "path": "demo13/pkg/version/version.go",
    "content": "package version\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n)\n\n// Info contains versioning information.\ntype Info struct {\n\tGitTag       string `json:\"gitTag\"`\n\tGitCommit    string `json:\"gitCommit\"`\n\tGitTreeState string `json:\"gitTreeState\"`\n\tBuildDate    string `json:\"buildDate\"`\n\tGoVersion    string `json:\"goVersion\"`\n\tCompiler     string `json:\"compiler\"`\n\tPlatform     string `json:\"platform\"`\n}\n\n// String returns info as a human-friendly version string.\nfunc (info Info) String() string {\n\treturn info.GitTag\n}\n\nfunc Get() Info {\n\treturn Info{\n\t\tGitTag:       gitTag,\n\t\tGitCommit:    gitCommit,\n\t\tGitTreeState: gitTreeState,\n\t\tBuildDate:    buildDate,\n\t\tGoVersion:    runtime.Version(),\n\t\tCompiler:     runtime.Compiler,\n\t\tPlatform:     fmt.Sprintf(\"%s/%s\", runtime.GOOS, runtime.GOARCH),\n\t}\n}\n"
  },
  {
    "path": "demo13/router/middleware/auth.go",
    "content": "package middleware\n\nimport (\n\t\"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/pkg/token\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc AuthMiddleware() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// Parse the json web token.\n\t\tif _, err := token.ParseRequest(c); err != nil {\n\t\t\thandler.SendResponse(c, errno.ErrTokenInvalid, nil)\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "demo13/router/middleware/header.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// NoCache is a middleware function that appends headers\n// to prevent the client from caching the HTTP response.\nfunc NoCache(c *gin.Context) {\n\tc.Header(\"Cache-Control\", \"no-cache, no-store, max-age=0, must-revalidate, value\")\n\tc.Header(\"Expires\", \"Thu, 01 Jan 1970 00:00:00 GMT\")\n\tc.Header(\"Last-Modified\", time.Now().UTC().Format(http.TimeFormat))\n\tc.Next()\n}\n\n// Options is a middleware function that appends headers\n// for options requests and aborts then exits the middleware\n// chain and ends the request.\nfunc Options(c *gin.Context) {\n\tif c.Request.Method != \"OPTIONS\" {\n\t\tc.Next()\n\t} else {\n\t\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\t\tc.Header(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Access-Control-Allow-Headers\", \"authorization, origin, content-type, accept\")\n\t\tc.Header(\"Allow\", \"HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Content-Type\", \"application/json\")\n\t\tc.AbortWithStatus(200)\n\t}\n}\n\n// Secure is a middleware function that appends security\n// and resource access headers.\nfunc Secure(c *gin.Context) {\n\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\tc.Header(\"X-Frame-Options\", \"DENY\")\n\tc.Header(\"X-Content-Type-Options\", \"nosniff\")\n\tc.Header(\"X-XSS-Protection\", \"1; mode=block\")\n\tif c.Request.TLS != nil {\n\t\tc.Header(\"Strict-Transport-Security\", \"max-age=31536000\")\n\t}\n\n\t// Also consider adding Content-Security-Policy headers\n\t// c.Header(\"Content-Security-Policy\", \"script-src 'self' https://cdnjs.cloudflare.com\")\n}\n"
  },
  {
    "path": "demo13/router/middleware/logging.go",
    "content": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/willf/pad\"\n)\n\ntype bodyLogWriter struct {\n\tgin.ResponseWriter\n\tbody *bytes.Buffer\n}\n\nfunc (w bodyLogWriter) Write(b []byte) (int, error) {\n\tw.body.Write(b)\n\treturn w.ResponseWriter.Write(b)\n}\n\n// Logging is a middleware function that logs the each request.\nfunc Logging() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tstart := time.Now().UTC()\n\t\tpath := c.Request.URL.Path\n\n\t\treg := regexp.MustCompile(\"(/v1/user|/login)\")\n\t\tif !reg.MatchString(path) {\n\t\t\treturn\n\t\t}\n\n\t\t// Skip for the health check requests.\n\t\tif path == \"/sd/health\" || path == \"/sd/ram\" || path == \"/sd/cpu\" || path == \"/sd/disk\" {\n\t\t\treturn\n\t\t}\n\n\t\t// Read the Body content\n\t\tvar bodyBytes []byte\n\t\tif c.Request.Body != nil {\n\t\t\tbodyBytes, _ = ioutil.ReadAll(c.Request.Body)\n\t\t}\n\n\t\t// Restore the io.ReadCloser to its original state\n\t\tc.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))\n\n\t\t// The basic informations.\n\t\tmethod := c.Request.Method\n\t\tip := c.ClientIP()\n\n\t\t//log.Debugf(\"New request come in, path: %s, Method: %s, body `%s`\", path, method, string(bodyBytes))\n\t\tblw := &bodyLogWriter{\n\t\t\tbody:           bytes.NewBufferString(\"\"),\n\t\t\tResponseWriter: c.Writer,\n\t\t}\n\t\tc.Writer = blw\n\n\t\t// Continue.\n\t\tc.Next()\n\n\t\t// Calculates the latency.\n\t\tend := time.Now().UTC()\n\t\tlatency := end.Sub(start)\n\n\t\tcode, message := -1, \"\"\n\n\t\t// get code and message\n\t\tvar response handler.Response\n\t\tif err := json.Unmarshal(blw.body.Bytes(), &response); err != nil {\n\t\t\tlog.Errorf(err, \"response body can not unmarshal to model.Response struct, body: `%s`\", blw.body.Bytes())\n\t\t\tcode = errno.InternalServerError.Code\n\t\t\tmessage = err.Error()\n\t\t} else {\n\t\t\tcode = response.Code\n\t\t\tmessage = response.Message\n\t\t}\n\n\t\tlog.Infof(\"%-13s | %-12s | %s %s | {code: %d, message: %s}\", latency, ip, pad.Right(method, 5, \"\"), path, code, message)\n\t}\n}\n"
  },
  {
    "path": "demo13/router/middleware/requestid.go",
    "content": "package middleware\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/satori/go.uuid\"\n)\n\nfunc RequestId() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// Check for incoming header, use it if exists\n\t\trequestId := c.Request.Header.Get(\"X-Request-Id\")\n\n\t\t// Create request id with UUID4\n\t\tif requestId == \"\" {\n\t\t\tu4, _ := uuid.NewV4()\n\t\t\trequestId = u4.String()\n\t\t}\n\n\t\t// Expose it for use in the application\n\t\tc.Set(\"X-Request-Id\", requestId)\n\n\t\t// Set X-Request-Id header\n\t\tc.Writer.Header().Set(\"X-Request-Id\", requestId)\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "demo13/router/router.go",
    "content": "package router\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/handler/sd\"\n\t\"apiserver/handler/user\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Load loads the middlewares, routes, handlers.\nfunc Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {\n\t// Middlewares.\n\tg.Use(gin.Recovery())\n\tg.Use(middleware.NoCache)\n\tg.Use(middleware.Options)\n\tg.Use(middleware.Secure)\n\tg.Use(mw...)\n\t// 404 Handler.\n\tg.NoRoute(func(c *gin.Context) {\n\t\tc.String(http.StatusNotFound, \"The incorrect API route.\")\n\t})\n\n\t// api for authentication functionalities\n\tg.POST(\"/login\", user.Login)\n\n\t// The user handlers, requiring authentication\n\tu := g.Group(\"/v1/user\")\n\tu.Use(middleware.AuthMiddleware())\n\t{\n\t\tu.POST(\"\", user.Create)\n\t\tu.DELETE(\"/:id\", user.Delete)\n\t\tu.PUT(\"/:id\", user.Update)\n\t\tu.GET(\"\", user.List)\n\t\tu.GET(\"/:username\", user.Get)\n\t}\n\n\t// The health check handlers\n\tsvcd := g.Group(\"/sd\")\n\t{\n\t\tsvcd.GET(\"/health\", sd.HealthCheck)\n\t\tsvcd.GET(\"/disk\", sd.DiskCheck)\n\t\tsvcd.GET(\"/cpu\", sd.CPUCheck)\n\t\tsvcd.GET(\"/ram\", sd.RAMCheck)\n\t}\n\n\treturn g\n}\n"
  },
  {
    "path": "demo13/service/service.go",
    "content": "package service\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\n\t\"apiserver/model\"\n\t\"apiserver/util\"\n)\n\nfunc ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) {\n\tinfos := make([]*model.UserInfo, 0)\n\tusers, count, err := model.ListUser(username, offset, limit)\n\tif err != nil {\n\t\treturn nil, count, err\n\t}\n\n\tids := []uint64{}\n\tfor _, user := range users {\n\t\tids = append(ids, user.Id)\n\t}\n\n\twg := sync.WaitGroup{}\n\tuserList := model.UserList{\n\t\tLock:  new(sync.Mutex),\n\t\tIdMap: make(map[uint64]*model.UserInfo, len(users)),\n\t}\n\n\terrChan := make(chan error, 1)\n\tfinished := make(chan bool, 1)\n\n\t// Improve query efficiency in parallel\n\tfor _, u := range users {\n\t\twg.Add(1)\n\t\tgo func(u *model.UserModel) {\n\t\t\tdefer wg.Done()\n\n\t\t\tshortId, err := util.GenShortId()\n\t\t\tif err != nil {\n\t\t\t\terrChan <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tuserList.Lock.Lock()\n\t\t\tdefer userList.Lock.Unlock()\n\t\t\tuserList.IdMap[u.Id] = &model.UserInfo{\n\t\t\t\tId:        u.Id,\n\t\t\t\tUsername:  u.Username,\n\t\t\t\tSayHello:  fmt.Sprintf(\"Hello %s\", shortId),\n\t\t\t\tPassword:  u.Password,\n\t\t\t\tCreatedAt: u.CreatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t\tUpdatedAt: u.UpdatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t}\n\t\t}(u)\n\t}\n\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(finished)\n\t}()\n\n\tselect {\n\tcase <-finished:\n\tcase err := <-errChan:\n\t\treturn nil, count, err\n\t}\n\n\tfor _, id := range ids {\n\t\tinfos = append(infos, userList.IdMap[id])\n\t}\n\n\treturn infos, count, nil\n}\n"
  },
  {
    "path": "demo13/util/util.go",
    "content": "package util\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/teris-io/shortid\"\n)\n\nfunc GenShortId() (string, error) {\n\treturn shortid.Generate()\n}\n\nfunc GetReqID(c *gin.Context) string {\n\tv, ok := c.Get(\"X-Request-Id\")\n\tif !ok {\n\t\treturn \"\"\n\t}\n\tif requestId, ok := v.(string); ok {\n\t\treturn requestId\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "demo14/Makefile",
    "content": "SHELL := /bin/bash\nBASEDIR = $(shell pwd)\n\n# build with verison infos\nversionDir = \"apiserver/pkg/version\"\ngitTag = $(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)\nbuildDate = $(shell TZ=Asia/Shanghai date +%FT%T%z)\ngitCommit = $(shell git log --pretty=format:'%H' -n 1)\ngitTreeState = $(shell if git status|grep -q 'clean';then echo clean; else echo dirty; fi)\n\nldflags=\"-w -X ${versionDir}.gitTag=${gitTag} -X ${versionDir}.buildDate=${buildDate} -X ${versionDir}.gitCommit=${gitCommit} -X ${versionDir}.gitTreeState=${gitTreeState}\"\n\nall: gotool\n\t@go build -v -ldflags ${ldflags} .\nclean:\n\trm -f apiserver\n\tfind . -name \"[._]*.s[a-w][a-z]\" | xargs -i rm -f {}\ngotool:\n\tgofmt -w .\n\tgo tool vet . |& grep -v vendor;true\nca:\n\topenssl 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\"\n\nhelp:\n\t@echo \"make - compile the source code\"\n\t@echo \"make clean - remove binary file and vim swp files\"\n\t@echo \"make gotool - run go tool 'fmt' and 'vet'\"\n\t@echo \"make ca - generate ca files\"\n\n.PHONY: clean gotool ca help\n\n\n"
  },
  {
    "path": "demo14/README.md",
    "content": "进阶：基于Nginx的API部署方案\n"
  },
  {
    "path": "demo14/admin.sh",
    "content": "#!/bin/bash\n\nSERVER=\"apiserver\"\nBASE_DIR=$PWD\nINTERVAL=2\n\n# 命令行参数，需要手动指定\nARGS=\"\"\n\nfunction start()\n{\n\tif [ \"`pgrep $SERVER -u $UID`\" != \"\" ];then\n\t\techo \"$SERVER already running\"\n\t\texit 1\n\tfi\n\n\tnohup $BASE_DIR/$SERVER $ARGS  server &>/dev/null &\n\n\techo \"sleeping...\" &&  sleep $INTERVAL\n\n\t# check status\n\tif [ \"`pgrep $SERVER -u $UID`\" == \"\" ];then\n\t\techo \"$SERVER start failed\"\n\t\texit 1\n\tfi\n}\n\nfunction status() \n{\n\tif [ \"`pgrep $SERVER -u $UID`\" != \"\" ];then\n\t\techo $SERVER is running\n\telse\n\t\techo $SERVER is not running\n\tfi\n}\n\nfunction stop() \n{\n\tif [ \"`pgrep $SERVER -u $UID`\" != \"\" ];then\n\t\tkill -9 `pgrep $SERVER -u $UID`\n\tfi\n\n\techo \"sleeping...\" &&  sleep $INTERVAL\n\n\tif [ \"`pgrep $SERVER -u $UID`\" != \"\" ];then\n\t\techo \"$SERVER stop failed\"\n\t\texit 1\n\tfi\n}\n\ncase \"$1\" in\n\t'start')\n\tstart\n\t;;  \n\t'stop')\n\tstop\n\t;;  \n\t'status')\n\tstatus\n\t;;  \n\t'restart')\n\tstop && start\n\t;;  \n\t*)  \n\techo \"usage: $0 {start|stop|restart|status}\"\n\texit 1\n\t;;  \nesac\n"
  },
  {
    "path": "demo14/conf/config.yaml",
    "content": "runmode: debug                 # 开发模式, debug, release, test\naddr: :8080                  # HTTP绑定端口\nname: apiserver              # API Server的名字\nurl: http://127.0.0.1:8080   # pingServer函数请求的API服务器的ip:port\nmax_ping_count: 10           # pingServer函数try的次数\njwt_secret: Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5\ntls:\n  addr: :8081\n  cert: conf/server.crt\n  key: conf/server.key\nlog:\n  writers: stdout\n  logger_level: DEBUG\n  logger_file: log/apiserver.log\n  log_format_text: true\n  rollingPolicy: size\n  log_rotate_date: 1\n  log_rotate_size: 1\n  log_backup_count: 7\ndb:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\ndocker_db:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\n"
  },
  {
    "path": "demo14/conf/server.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIID2TCCAsGgAwIBAgIJAI/8axTNy37vMA0GCSqGSIb3DQEBBQUAMIGCMQswCQYD\nVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO\nUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAkxMjcuMC4wLjEx\nGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNvbTAeFw0xODA2MDQwNjMwMzFaFw0y\nODA2MDEwNjMwMzFaMIGCMQswCQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYD\nVQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklU\nMRIwEAYDVQQDDAkxMjcuMC4wLjExGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNv\nbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKHbnpmaxHokFC8rnJlG\n42eL4C3tog7rffv96DrwDajdFNk8DeaNUpZ+R4X4YPecumsCsbol2dDC1IHHGJUN\nq/A1vRjyDptMLmzp30p1aqVPcz+j8GMXtZUflLgup+HeKZRdrsk/0SaH1C3ywUBm\nyDMNHUxEvoFj0x9aJoPutnkEk9miZt/kF2vxlTbKFM14/TecVr3ljNwTebJAqEw9\niaZpNXn/IQV2I2PTGlxjyT6XNsCchjMTJ9y/XS9rBeX6BFHgQsV52CW1mWz8FieM\n/nyF9hE/TZKCpMtHWEYIdBbrxxY0BlhMxEtyIFzTha5zltFrQJGUYz7RIlyuabjk\nor8CAwEAAaNQME4wHQYDVR0OBBYEFIjLBQlToqUDoyOLeUd6HeaqiiLJMB8GA1Ud\nIwQYMBaAFIjLBQlToqUDoyOLeUd6HeaqiiLJMAwGA1UdEwQFMAMBAf8wDQYJKoZI\nhvcNAQEFBQADggEBAHAqpqGNUNgYCr3JvrVULDqFKmqLpvDKfLd6kmtnFzvuZLSz\nANE2RXXqQ5Ms3F0oHqZQq4aOvgvfas5dBLoToPL/ip+GibQ7O/1PsPD5bZaczUel\nAnmClX4ewcOEWmZx9mbYOz2C8RREvKU3MoBERrWA33LquYH0+8W8WHyMKNNF97oD\nBgWZ8R1t3N59ySZyHcye7ddlJUubRbdyCGrHbmziHV3Cj61NJiwS2JRvhYkc71M6\n8921j63qEM6i0NusFYG/w0GM3x/OeaeY/om6x+IOhJp3vIa1JPSekVyNSCwDEc3B\n+8WmVFaeMMXwErSkuu8nCbqRP8J1jiKgzpG/92g=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "demo14/conf/server.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCh256ZmsR6JBQv\nK5yZRuNni+At7aIO6337/eg68A2o3RTZPA3mjVKWfkeF+GD3nLprArG6JdnQwtSB\nxxiVDavwNb0Y8g6bTC5s6d9KdWqlT3M/o/BjF7WVH5S4Lqfh3imUXa7JP9Emh9Qt\n8sFAZsgzDR1MRL6BY9MfWiaD7rZ5BJPZombf5Bdr8ZU2yhTNeP03nFa95YzcE3my\nQKhMPYmmaTV5/yEFdiNj0xpcY8k+lzbAnIYzEyfcv10vawXl+gRR4ELFedgltZls\n/BYnjP58hfYRP02SgqTLR1hGCHQW68cWNAZYTMRLciBc04Wuc5bRa0CRlGM+0SJc\nrmm45KK/AgMBAAECggEASNMWvgf7pPT8u+iEchaKFLnDqQaFZu8f5TRtu67shnDK\ng59YpcYqRZoVtjp17pLu8Vzp+FY1dY9jq+yXq+DV3qNfLI0kc01IiiqEE+1WiYCA\n2z541y0Av1LRSDl9wcuCq8Wm8der1AlDN1VFDCPyqb2Z1AoOKQtwH2ghcjUCltn4\nNxuMYOVdql+Idl/7PCEha9pW3DQvPqJVXRPvBh6tB7FFuUOIq4+EFRUWWZlyCJfQ\nfYxB9jcFUWmAUieV3iBajg6H6QVkj/STLSgKuSktEBM2mqyL4slHXFWRt7+NQikg\ntOEWnqcTC4Ei2Dyam4SBXHJ1wT8AJV4kMTrN1ZBRmQKBgQDOMcIGHyuBI1aHcvPW\nTkein7bslFAKsA1LBqnw+alWzSHMKEmfWbBjeW0FQTIGEYMXwIq5JhnNcS5Kb5Qt\nBZgbBPDb7S80DtJnkno3iJ6hpijE3X/29lezigIuGibO5MD0Na1am2BP0iU77ifV\nxpYvj/j/pkdcTLM7hRDImSeNIwKBgQDI9EUFkGi6H9NZEles+MCHenVRYUBT2PXe\nhmtd9d5f4v3CmRsXvl4Dl/slIrJ9kuZktAck3Ocx6D0hnZcj1mwq077K7H3QqKCl\nzRwB11H5v/imsZ1ey0hLVPhCzr2miRPM/9oL5B2st9eotzhyRcrMn+oYZSSt79S8\nQ3YDiZ7TtQKBgENSr7z79GJnvVrgR4kTagRJDZrVGgVDUjPK6zXI7mdu9rgH93HW\nAOeZv+TVUpX0pc7diO3G6OnRKIIZSFIi33UC+fl0ydK/fCdhBhKXwuOYsvsEL0Hd\nUOlICEoxM7adrfqOhBlvXdTyEkItEkiUXHkPEwe1rNsQF/05By/YAbftAoGAYB2F\njd2+WZezTN0bFl58J9CIoH31eKVDJEYCwJRC4nX9jcARV0/0Q5/DvcVUvf8vN2ds\nK1OFOTetVZC8o6WBYxKYJRLsMosVG3h5NuA4E06grYoyjQ6J644emEWuLCNQVzLg\npeNb1iqweb/4vZ9oGms6WqS14IPfqpRRs+t1DikCgYEAyFtQKlLmNm/EirOMqjS2\n4/zMRj4B6n8BB8ZJsVC/MkuD+4B03rY44pP324cJnqVIcug9Zoijffnh1qbtZEc/\nMhNCa4a2MY0qcd4r/43cGbjqv94En7e5TgkH/79W+lbR3uBayt33RuuKStauZkf/\n2eBi4LlGO5stoeD63h9Ten0=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "demo14/config/config.go",
    "content": "package config\n\nimport (\n\t\"strings\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n)\n\ntype Config struct {\n\tName string\n}\n\nfunc Init(cfg string) error {\n\tc := Config{\n\t\tName: cfg,\n\t}\n\n\t// 初始化配置文件\n\tif err := c.initConfig(); err != nil {\n\t\treturn err\n\t}\n\n\t// 初始化日志包\n\tc.initLog()\n\n\t// 监控配置文件变化并热加载程序\n\tc.watchConfig()\n\n\treturn nil\n}\n\nfunc (c *Config) initConfig() error {\n\tif c.Name != \"\" {\n\t\tviper.SetConfigFile(c.Name) // 如果指定了配置文件，则解析指定的配置文件\n\t} else {\n\t\tviper.AddConfigPath(\"conf\") // 如果没有指定配置文件，则解析默认的配置文件\n\t\tviper.SetConfigName(\"config\")\n\t}\n\tviper.SetConfigType(\"yaml\")     // 设置配置文件格式为YAML\n\tviper.AutomaticEnv()            // 读取匹配的环境变量\n\tviper.SetEnvPrefix(\"APISERVER\") // 读取环境变量的前缀为APISERVER\n\treplacer := strings.NewReplacer(\".\", \"_\")\n\tviper.SetEnvKeyReplacer(replacer)\n\tif err := viper.ReadInConfig(); err != nil { // viper解析配置文件\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *Config) initLog() {\n\tpassLagerCfg := log.PassLagerCfg{\n\t\tWriters:        viper.GetString(\"log.writers\"),\n\t\tLoggerLevel:    viper.GetString(\"log.logger_level\"),\n\t\tLoggerFile:     viper.GetString(\"log.logger_file\"),\n\t\tLogFormatText:  viper.GetBool(\"log.log_format_text\"),\n\t\tRollingPolicy:  viper.GetString(\"log.rollingPolicy\"),\n\t\tLogRotateDate:  viper.GetInt(\"log.log_rotate_date\"),\n\t\tLogRotateSize:  viper.GetInt(\"log.log_rotate_size\"),\n\t\tLogBackupCount: viper.GetInt(\"log.log_backup_count\"),\n\t}\n\n\tlog.InitWithConfig(&passLagerCfg)\n}\n\n// 监控配置文件变化并热加载程序\nfunc (c *Config) watchConfig() {\n\tviper.WatchConfig()\n\tviper.OnConfigChange(func(e fsnotify.Event) {\n\t\tlog.Infof(\"Config file changed: %s\", e.Name)\n\t})\n}\n"
  },
  {
    "path": "demo14/db.sql",
    "content": "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n/*!40101 SET NAMES utf8 */;\n/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;\n/*!40103 SET TIME_ZONE='+00:00' */;\n/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;\n/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n\nCREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */;\n\nUSE `db_apiserver`;\n\n--\n-- Table structure for table `tb_users`\n--\n\nDROP TABLE IF EXISTS `tb_users`;\n/*!40101 SET @saved_cs_client     = @@character_set_client */;\n/*!40101 SET character_set_client = utf8 */;\nCREATE TABLE `tb_users` (\n  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n  `username` varchar(255) NOT NULL,\n  `password` varchar(255) NOT NULL,\n  `createdAt` timestamp NULL DEFAULT NULL,\n  `updatedAt` timestamp NULL DEFAULT NULL,\n  `deletedAt` timestamp NULL DEFAULT NULL,\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `username` (`username`),\n  KEY `idx_tb_users_deletedAt` (`deletedAt`)\n) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;\n/*!40101 SET character_set_client = @saved_cs_client */;\n\n--\n-- Dumping data for table `tb_users`\n--\n\nLOCK TABLES `tb_users` WRITE;\n/*!40000 ALTER TABLE `tb_users` DISABLE KEYS */;\nINSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL);\n/*!40000 ALTER TABLE `tb_users` ENABLE KEYS */;\nUNLOCK TABLES;\n/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;\n\n/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;\n/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;\n/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n\n-- Dump completed on 2018-05-28  0:25:41\n"
  },
  {
    "path": "demo14/handler/handler.go",
    "content": "package handler\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype Response struct {\n\tCode    int         `json:\"code\"`\n\tMessage string      `json:\"message\"`\n\tData    interface{} `json:\"data\"`\n}\n\nfunc SendResponse(c *gin.Context, err error, data interface{}) {\n\tcode, message := errno.DecodeErr(err)\n\n\t// always return http.StatusOK\n\tc.JSON(http.StatusOK, Response{\n\t\tCode:    code,\n\t\tMessage: message,\n\t\tData:    data,\n\t})\n}\n"
  },
  {
    "path": "demo14/handler/sd/check.go",
    "content": "package sd\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/shirou/gopsutil/cpu\"\n\t\"github.com/shirou/gopsutil/disk\"\n\t\"github.com/shirou/gopsutil/load\"\n\t\"github.com/shirou/gopsutil/mem\"\n)\n\nconst (\n\tB  = 1\n\tKB = 1024 * B\n\tMB = 1024 * KB\n\tGB = 1024 * MB\n)\n\n// HealthCheck shows `OK` as the ping-pong result.\nfunc HealthCheck(c *gin.Context) {\n\tmessage := \"OK\"\n\tc.String(http.StatusOK, \"\\n\"+message)\n}\n\n// DiskCheck checks the disk usage.\nfunc DiskCheck(c *gin.Context) {\n\tu, _ := disk.Usage(\"/\")\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusOK\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n\n// CPUCheck checks the cpu usage.\nfunc CPUCheck(c *gin.Context) {\n\tcores, _ := cpu.Counts(false)\n\n\ta, _ := load.Avg()\n\tl1 := a.Load1\n\tl5 := a.Load5\n\tl15 := a.Load15\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif l5 >= float64(cores-1) {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if l5 >= float64(cores-2) {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Load average: %.2f, %.2f, %.2f | Cores: %d\", text, l1, l5, l15, cores)\n\tc.String(status, \"\\n\"+message)\n}\n\n// RAMCheck checks the disk usage.\nfunc RAMCheck(c *gin.Context) {\n\tu, _ := mem.VirtualMemory()\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n"
  },
  {
    "path": "demo14/handler/user/create.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// Create creates a new user account.\nfunc Create(c *gin.Context) {\n\tlog.Info(\"User Create function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\tvar r CreateRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tu := model.UserModel{\n\t\tUsername: r.Username,\n\t\tPassword: r.Password,\n\t}\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\t// Insert the user to the database.\n\tif err := u.Create(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\trsp := CreateResponse{\n\t\tUsername: r.Username,\n\t}\n\n\t// Show the user information.\n\tSendResponse(c, nil, rsp)\n}\n"
  },
  {
    "path": "demo14/handler/user/delete.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Delete delete an user by the user identifier.\nfunc Delete(c *gin.Context) {\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\tif err := model.DeleteUser(uint64(userId)); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo14/handler/user/get.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Get gets an user by the user identifier.\nfunc Get(c *gin.Context) {\n\tusername := c.Param(\"username\")\n\t// Get the user by the `username` from the database.\n\tuser, err := model.GetUser(username)\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrUserNotFound, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, user)\n}\n"
  },
  {
    "path": "demo14/handler/user/list.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/service\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n)\n\n// List list the users in the database.\nfunc List(c *gin.Context) {\n\tlog.Info(\"List function called.\")\n\tvar r ListRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tinfos, count, err := service.ListUser(r.Username, r.Offset, r.Limit)\n\tif err != nil {\n\t\tSendResponse(c, err, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, ListResponse{\n\t\tTotalCount: count,\n\t\tUserList:   infos,\n\t})\n}\n"
  },
  {
    "path": "demo14/handler/user/login.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/auth\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/pkg/token\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Login generates the authentication token\n// if the password was matched with the specified account.\nfunc Login(c *gin.Context) {\n\t// Binding the data with the user struct.\n\tvar u model.UserModel\n\tif err := c.Bind(&u); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\t// Get the user information by the login username.\n\td, err := model.GetUser(u.Username)\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrUserNotFound, nil)\n\t\treturn\n\t}\n\n\t// Compare the login password with the user password.\n\tif err := auth.Compare(d.Password, u.Password); err != nil {\n\t\tSendResponse(c, errno.ErrPasswordIncorrect, nil)\n\t\treturn\n\t}\n\n\t// Sign the json web token.\n\tt, err := token.Sign(c, token.Context{ID: d.Id, Username: d.Username}, \"\")\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrToken, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, model.Token{Token: t})\n}\n"
  },
  {
    "path": "demo14/handler/user/update.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// Update update a exist user account info.\nfunc Update(c *gin.Context) {\n\tlog.Info(\"Update function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\t// Get the user id from the url parameter.\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\n\t// Binding the user data.\n\tvar u model.UserModel\n\tif err := c.Bind(&u); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\t// We update the record based on the user id.\n\tu.Id = uint64(userId)\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\n\t// Save changed fields.\n\tif err := u.Update(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo14/handler/user/user.go",
    "content": "package user\n\nimport (\n\t\"apiserver/model\"\n)\n\ntype CreateRequest struct {\n\tUsername string `json:\"username\"`\n\tPassword string `json:\"password\"`\n}\n\ntype CreateResponse struct {\n\tUsername string `json:\"username\"`\n}\n\ntype ListRequest struct {\n\tUsername string `json:\"username\"`\n\tOffset   int    `json:\"offset\"`\n\tLimit    int    `json:\"limit\"`\n}\n\ntype ListResponse struct {\n\tTotalCount uint64            `json:\"totalCount\"`\n\tUserList   []*model.UserInfo `json:\"userList\"`\n}\n"
  },
  {
    "path": "demo14/main.go",
    "content": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n\n\t\"apiserver/config\"\n\t\"apiserver/model\"\n\tv \"apiserver/pkg/version\"\n\t\"apiserver/router\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tcfg     = pflag.StringP(\"config\", \"c\", \"\", \"apiserver config file path.\")\n\tversion = pflag.BoolP(\"version\", \"v\", false, \"show version info.\")\n)\n\nfunc main() {\n\tpflag.Parse()\n\tif *version {\n\t\tv := v.Get()\n\t\tmarshalled, err := json.MarshalIndent(&v, \"\", \"  \")\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"%v\\n\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tfmt.Println(string(marshalled))\n\t\treturn\n\t}\n\n\t// init config\n\tif err := config.Init(*cfg); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// init db\n\tmodel.DB.Init()\n\tdefer model.DB.Close()\n\n\t// Set gin mode.\n\tgin.SetMode(viper.GetString(\"runmode\"))\n\n\t// Create the Gin engine.\n\tg := gin.New()\n\n\t// Routes.\n\trouter.Load(\n\t\t// Cores.\n\t\tg,\n\n\t\t// Middlwares.\n\t\tmiddleware.Logging(),\n\t\tmiddleware.RequestId(),\n\t)\n\n\t// Ping the server to make sure the router is working.\n\tgo func() {\n\t\tif err := pingServer(); err != nil {\n\t\t\tlog.Fatal(\"The router has no response, or it might took too long to start up.\", err)\n\t\t}\n\t\tlog.Info(\"The router has been deployed successfully.\")\n\t}()\n\n\t// Start to listening the incoming requests.\n\tcert := viper.GetString(\"tls.cert\")\n\tkey := viper.GetString(\"tls.key\")\n\tif cert != \"\" && key != \"\" {\n\t\tgo func() {\n\t\t\tlog.Infof(\"Start to listening the incoming requests on https address: %s\", viper.GetString(\"tls.addr\"))\n\t\t\tlog.Info(http.ListenAndServeTLS(viper.GetString(\"tls.addr\"), cert, key, g).Error())\n\t\t}()\n\t}\n\n\tlog.Infof(\"Start to listening the incoming requests on http address: %s\", viper.GetString(\"addr\"))\n\tlog.Info(http.ListenAndServe(viper.GetString(\"addr\"), g).Error())\n}\n\n// pingServer pings the http server to make sure the router is working.\nfunc pingServer() error {\n\tfor i := 0; i < viper.GetInt(\"max_ping_count\"); i++ {\n\t\t// Ping the server by sending a GET request to `/health`.\n\t\tresp, err := http.Get(viper.GetString(\"url\") + \"/sd/health\")\n\t\tif err == nil && resp.StatusCode == 200 {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Sleep for a second to continue the next ping.\n\t\tlog.Info(\"Waiting for the router, retry in 1 second.\")\n\t\ttime.Sleep(time.Second)\n\t}\n\treturn errors.New(\"Cannot connect to the router.\")\n}\n"
  },
  {
    "path": "demo14/model/init.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n\t// MySQL driver.\n\t\"github.com/jinzhu/gorm\"\n\t_ \"github.com/jinzhu/gorm/dialects/mysql\"\n)\n\ntype Database struct {\n\tSelf   *gorm.DB\n\tDocker *gorm.DB\n}\n\nvar DB *Database\n\nfunc openDB(username, password, addr, name string) *gorm.DB {\n\tconfig := fmt.Sprintf(\"%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s\",\n\t\tusername,\n\t\tpassword,\n\t\taddr,\n\t\tname,\n\t\ttrue,\n\t\t//\"Asia/Shanghai\"),\n\t\t\"Local\")\n\n\tdb, err := gorm.Open(\"mysql\", config)\n\tif err != nil {\n\t\tlog.Errorf(err, \"Database connection failed. Database name: %s\", name)\n\t}\n\n\t// set for db connection\n\tsetupDB(db)\n\n\treturn db\n}\n\nfunc setupDB(db *gorm.DB) {\n\tdb.LogMode(viper.GetBool(\"gormlog\"))\n\t//db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数，默认值为0表示不限制.设置最大的连接数，可以避免并发太高导致连接mysql出现too many connections的错误。\n\tdb.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。\n}\n\n// used for cli\nfunc InitSelfDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"db.username\"),\n\t\tviper.GetString(\"db.password\"),\n\t\tviper.GetString(\"db.addr\"),\n\t\tviper.GetString(\"db.name\"))\n}\n\nfunc GetSelfDB() *gorm.DB {\n\treturn InitSelfDB()\n}\n\nfunc InitDockerDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"docker_db.username\"),\n\t\tviper.GetString(\"docker_db.password\"),\n\t\tviper.GetString(\"docker_db.addr\"),\n\t\tviper.GetString(\"docker_db.name\"))\n}\n\nfunc GetDockerDB() *gorm.DB {\n\treturn InitDockerDB()\n}\n\nfunc (db *Database) Init() {\n\tDB = &Database{\n\t\tSelf:   GetSelfDB(),\n\t\tDocker: GetDockerDB(),\n\t}\n}\n\nfunc (db *Database) Close() {\n\tDB.Self.Close()\n\tDB.Docker.Close()\n}\n"
  },
  {
    "path": "demo14/model/model.go",
    "content": "package model\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype BaseModel struct {\n\tId        uint64     `gorm:\"primary_key;AUTO_INCREMENT;column:id\" json:\"-\"`\n\tCreatedAt time.Time  `gorm:\"column:createdAt\" json:\"-\"`\n\tUpdatedAt time.Time  `gorm:\"column:updatedAt\" json:\"-\"`\n\tDeletedAt *time.Time `gorm:\"column:deletedAt\" sql:\"index\" json:\"-\"`\n}\n\ntype UserInfo struct {\n\tId        uint64 `json:\"id\"`\n\tUsername  string `json:\"username\"`\n\tSayHello  string `json:\"sayHello\"`\n\tPassword  string `json:\"password\"`\n\tCreatedAt string `json:\"createdAt\"`\n\tUpdatedAt string `json:\"updatedAt\"`\n}\n\ntype UserList struct {\n\tLock  *sync.Mutex\n\tIdMap map[uint64]*UserInfo\n}\n\n// Token represents a JSON web token.\ntype Token struct {\n\tToken string `json:\"token\"`\n}\n"
  },
  {
    "path": "demo14/model/user.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"apiserver/pkg/auth\"\n\t\"apiserver/pkg/constvar\"\n\n\tvalidator \"gopkg.in/go-playground/validator.v9\"\n)\n\n// User represents a registered user.\ntype UserModel struct {\n\tBaseModel\n\tUsername string `json:\"username\" gorm:\"column:username;not null\" binding:\"required\" validate:\"min=1,max=32\"`\n\tPassword string `json:\"password\" gorm:\"column:password;not null\" binding:\"required\" validate:\"min=5,max=128\"`\n}\n\nfunc (c *UserModel) TableName() string {\n\treturn \"tb_users\"\n}\n\n// Create creates a new user account.\nfunc (u *UserModel) Create() error {\n\treturn DB.Self.Create(&u).Error\n}\n\n// DeleteUser deletes the user by the user identifier.\nfunc DeleteUser(id uint64) error {\n\tuser := UserModel{}\n\tuser.BaseModel.Id = id\n\treturn DB.Self.Delete(&user).Error\n}\n\n// Update updates an user account information.\nfunc (u *UserModel) Update() error {\n\treturn DB.Self.Save(u).Error\n}\n\n// GetUser gets an user by the user identifier.\nfunc GetUser(username string) (*UserModel, error) {\n\tu := &UserModel{}\n\td := DB.Self.Where(\"username = ?\", username).First(&u)\n\treturn u, d.Error\n}\n\n// ListUser List all users\nfunc ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) {\n\tif limit == 0 {\n\t\tlimit = constvar.DefaultLimit\n\t}\n\n\tusers := make([]*UserModel, 0)\n\tvar count uint64\n\n\twhere := fmt.Sprintf(\"username like '%%%s%%'\", username)\n\tif err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\tif err := DB.Self.Where(where).Offset(offset).Limit(limit).Order(\"id desc\").Find(&users).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\treturn users, count, nil\n}\n\n// Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct).\nfunc (u *UserModel) Compare(pwd string) (err error) {\n\terr = auth.Compare(u.Password, pwd)\n\treturn\n}\n\n// Encrypt the user password.\nfunc (u *UserModel) Encrypt() (err error) {\n\tu.Password, err = auth.Encrypt(u.Password)\n\treturn\n}\n\n// Validate the fields.\nfunc (u *UserModel) Validate() error {\n\tvalidate := validator.New()\n\treturn validate.Struct(u)\n}\n"
  },
  {
    "path": "demo14/pkg/auth/auth.go",
    "content": "package auth\n\nimport \"golang.org/x/crypto/bcrypt\"\n\n// Encrypt encrypts the plain text with bcrypt.\nfunc Encrypt(source string) (string, error) {\n\thashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost)\n\treturn string(hashedBytes), err\n}\n\n// Compare compares the encrypted text with the plain text if it's the same.\nfunc Compare(hashedPassword, password string) error {\n\treturn bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))\n}\n"
  },
  {
    "path": "demo14/pkg/constvar/constvar.go",
    "content": "package constvar\n\nconst (\n\tDefaultLimit = 50\n)\n"
  },
  {
    "path": "demo14/pkg/errno/code.go",
    "content": "package errno\n\nvar (\n\t// Common errors\n\tOK                  = &Errno{Code: 0, Message: \"OK\"}\n\tInternalServerError = &Errno{Code: 10001, Message: \"Internal server error\"}\n\tErrBind             = &Errno{Code: 10002, Message: \"Error occurred while binding the request body to the struct.\"}\n\n\tErrValidation = &Errno{Code: 20001, Message: \"Validation failed.\"}\n\tErrDatabase   = &Errno{Code: 20002, Message: \"Database error.\"}\n\tErrToken      = &Errno{Code: 20003, Message: \"Error occurred while signing the JSON web token.\"}\n\n\t// user errors\n\tErrEncrypt           = &Errno{Code: 20101, Message: \"Error occurred while encrypting the user password.\"}\n\tErrUserNotFound      = &Errno{Code: 20102, Message: \"The user was not found.\"}\n\tErrTokenInvalid      = &Errno{Code: 20103, Message: \"The token was invalid.\"}\n\tErrPasswordIncorrect = &Errno{Code: 20104, Message: \"The password was incorrect.\"}\n)\n"
  },
  {
    "path": "demo14/pkg/errno/errno.go",
    "content": "package errno\n\nimport \"fmt\"\n\ntype Errno struct {\n\tCode    int\n\tMessage string\n}\n\nfunc (err Errno) Error() string {\n\treturn err.Message\n}\n\n// Err represents an error\ntype Err struct {\n\tCode    int\n\tMessage string\n\tErr     error\n}\n\nfunc New(errno *Errno, err error) *Err {\n\treturn &Err{Code: errno.Code, Message: errno.Message, Err: err}\n}\n\nfunc (err *Err) Add(message string) error {\n\terr.Message += \" \" + message\n\treturn err\n}\n\nfunc (err *Err) Addf(format string, args ...interface{}) error {\n\terr.Message += \" \" + fmt.Sprintf(format, args...)\n\treturn err\n}\n\nfunc (err *Err) Error() string {\n\treturn fmt.Sprintf(\"Err - code: %d, message: %s, error: %s\", err.Code, err.Message, err.Err)\n}\n\nfunc IsErrUserNotFound(err error) bool {\n\tcode, _ := DecodeErr(err)\n\treturn code == ErrUserNotFound.Code\n}\n\nfunc DecodeErr(err error) (int, string) {\n\tif err == nil {\n\t\treturn OK.Code, OK.Message\n\t}\n\n\tswitch typed := err.(type) {\n\tcase *Err:\n\t\treturn typed.Code, typed.Message\n\tcase *Errno:\n\t\treturn typed.Code, typed.Message\n\tdefault:\n\t}\n\n\treturn InternalServerError.Code, err.Error()\n}\n"
  },
  {
    "path": "demo14/pkg/token/token.go",
    "content": "package token\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\tjwt \"github.com/dgrijalva/jwt-go\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\t// ErrMissingHeader means the `Authorization` header was empty.\n\tErrMissingHeader = errors.New(\"The length of the `Authorization` header is zero.\")\n)\n\n// Context is the context of the JSON web token.\ntype Context struct {\n\tID       uint64\n\tUsername string\n}\n\n// secretFunc validates the secret format.\nfunc secretFunc(secret string) jwt.Keyfunc {\n\treturn func(token *jwt.Token) (interface{}, error) {\n\t\t// Make sure the `alg` is what we except.\n\t\tif _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {\n\t\t\treturn nil, jwt.ErrSignatureInvalid\n\t\t}\n\n\t\treturn []byte(secret), nil\n\t}\n}\n\n// Parse validates the token with the specified secret,\n// and returns the context if the token was valid.\nfunc Parse(tokenString string, secret string) (*Context, error) {\n\tctx := &Context{}\n\n\t// Parse the token.\n\ttoken, err := jwt.Parse(tokenString, secretFunc(secret))\n\n\t// Parse error.\n\tif err != nil {\n\t\treturn ctx, err\n\n\t\t// Read the token if it's valid.\n\t} else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {\n\t\tctx.ID = uint64(claims[\"id\"].(float64))\n\t\tctx.Username = claims[\"username\"].(string)\n\t\treturn ctx, nil\n\n\t\t// Other errors.\n\t} else {\n\t\treturn ctx, err\n\t}\n}\n\n// ParseRequest gets the token from the header and\n// pass it to the Parse function to parses the token.\nfunc ParseRequest(c *gin.Context) (*Context, error) {\n\theader := c.Request.Header.Get(\"Authorization\")\n\n\t// Load the jwt secret from config\n\tsecret := viper.GetString(\"jwt_secret\")\n\n\tif len(header) == 0 {\n\t\treturn &Context{}, ErrMissingHeader\n\t}\n\n\tvar t string\n\t// Parse the header to get the token part.\n\tfmt.Sscanf(header, \"Bearer %s\", &t)\n\treturn Parse(t, secret)\n}\n\n// Sign signs the context with the specified secret.\nfunc Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) {\n\t// Load the jwt secret from the Gin config if the secret isn't specified.\n\tif secret == \"\" {\n\t\tsecret = viper.GetString(\"jwt_secret\")\n\t}\n\t// The token content.\n\ttoken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{\n\t\t\"id\":       c.ID,\n\t\t\"username\": c.Username,\n\t\t\"nbf\":      time.Now().Unix(),\n\t\t\"iat\":      time.Now().Unix(),\n\t})\n\t// Sign the token with the specified secret.\n\ttokenString, err = token.SignedString([]byte(secret))\n\n\treturn\n}\n"
  },
  {
    "path": "demo14/pkg/version/base.go",
    "content": "package version\n\nvar (\n\tgitTag       string = \"\"\n\tgitCommit    string = \"$Format:%H$\"          // sha1 from git, output of $(git rev-parse HEAD)\n\tgitTreeState string = \"not a git tree\"       // state of git tree, either \"clean\" or \"dirty\"\n\tbuildDate    string = \"1970-01-01T00:00:00Z\" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')\n)\n"
  },
  {
    "path": "demo14/pkg/version/doc.go",
    "content": "package version\n"
  },
  {
    "path": "demo14/pkg/version/version.go",
    "content": "package version\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n)\n\n// Info contains versioning information.\ntype Info struct {\n\tGitTag       string `json:\"gitTag\"`\n\tGitCommit    string `json:\"gitCommit\"`\n\tGitTreeState string `json:\"gitTreeState\"`\n\tBuildDate    string `json:\"buildDate\"`\n\tGoVersion    string `json:\"goVersion\"`\n\tCompiler     string `json:\"compiler\"`\n\tPlatform     string `json:\"platform\"`\n}\n\n// String returns info as a human-friendly version string.\nfunc (info Info) String() string {\n\treturn info.GitTag\n}\n\nfunc Get() Info {\n\treturn Info{\n\t\tGitTag:       gitTag,\n\t\tGitCommit:    gitCommit,\n\t\tGitTreeState: gitTreeState,\n\t\tBuildDate:    buildDate,\n\t\tGoVersion:    runtime.Version(),\n\t\tCompiler:     runtime.Compiler,\n\t\tPlatform:     fmt.Sprintf(\"%s/%s\", runtime.GOOS, runtime.GOARCH),\n\t}\n}\n"
  },
  {
    "path": "demo14/router/middleware/auth.go",
    "content": "package middleware\n\nimport (\n\t\"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/pkg/token\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc AuthMiddleware() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// Parse the json web token.\n\t\tif _, err := token.ParseRequest(c); err != nil {\n\t\t\thandler.SendResponse(c, errno.ErrTokenInvalid, nil)\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "demo14/router/middleware/header.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// NoCache is a middleware function that appends headers\n// to prevent the client from caching the HTTP response.\nfunc NoCache(c *gin.Context) {\n\tc.Header(\"Cache-Control\", \"no-cache, no-store, max-age=0, must-revalidate, value\")\n\tc.Header(\"Expires\", \"Thu, 01 Jan 1970 00:00:00 GMT\")\n\tc.Header(\"Last-Modified\", time.Now().UTC().Format(http.TimeFormat))\n\tc.Next()\n}\n\n// Options is a middleware function that appends headers\n// for options requests and aborts then exits the middleware\n// chain and ends the request.\nfunc Options(c *gin.Context) {\n\tif c.Request.Method != \"OPTIONS\" {\n\t\tc.Next()\n\t} else {\n\t\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\t\tc.Header(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Access-Control-Allow-Headers\", \"authorization, origin, content-type, accept\")\n\t\tc.Header(\"Allow\", \"HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Content-Type\", \"application/json\")\n\t\tc.AbortWithStatus(200)\n\t}\n}\n\n// Secure is a middleware function that appends security\n// and resource access headers.\nfunc Secure(c *gin.Context) {\n\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\tc.Header(\"X-Frame-Options\", \"DENY\")\n\tc.Header(\"X-Content-Type-Options\", \"nosniff\")\n\tc.Header(\"X-XSS-Protection\", \"1; mode=block\")\n\tif c.Request.TLS != nil {\n\t\tc.Header(\"Strict-Transport-Security\", \"max-age=31536000\")\n\t}\n\n\t// Also consider adding Content-Security-Policy headers\n\t// c.Header(\"Content-Security-Policy\", \"script-src 'self' https://cdnjs.cloudflare.com\")\n}\n"
  },
  {
    "path": "demo14/router/middleware/logging.go",
    "content": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/willf/pad\"\n)\n\ntype bodyLogWriter struct {\n\tgin.ResponseWriter\n\tbody *bytes.Buffer\n}\n\nfunc (w bodyLogWriter) Write(b []byte) (int, error) {\n\tw.body.Write(b)\n\treturn w.ResponseWriter.Write(b)\n}\n\n// Logging is a middleware function that logs the each request.\nfunc Logging() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tstart := time.Now().UTC()\n\t\tpath := c.Request.URL.Path\n\n\t\treg := regexp.MustCompile(\"(/v1/user|/login)\")\n\t\tif !reg.MatchString(path) {\n\t\t\treturn\n\t\t}\n\n\t\t// Skip for the health check requests.\n\t\tif path == \"/sd/health\" || path == \"/sd/ram\" || path == \"/sd/cpu\" || path == \"/sd/disk\" {\n\t\t\treturn\n\t\t}\n\n\t\t// Read the Body content\n\t\tvar bodyBytes []byte\n\t\tif c.Request.Body != nil {\n\t\t\tbodyBytes, _ = ioutil.ReadAll(c.Request.Body)\n\t\t}\n\n\t\t// Restore the io.ReadCloser to its original state\n\t\tc.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))\n\n\t\t// The basic informations.\n\t\tmethod := c.Request.Method\n\t\tip := c.ClientIP()\n\n\t\t//log.Debugf(\"New request come in, path: %s, Method: %s, body `%s`\", path, method, string(bodyBytes))\n\t\tblw := &bodyLogWriter{\n\t\t\tbody:           bytes.NewBufferString(\"\"),\n\t\t\tResponseWriter: c.Writer,\n\t\t}\n\t\tc.Writer = blw\n\n\t\t// Continue.\n\t\tc.Next()\n\n\t\t// Calculates the latency.\n\t\tend := time.Now().UTC()\n\t\tlatency := end.Sub(start)\n\n\t\tcode, message := -1, \"\"\n\n\t\t// get code and message\n\t\tvar response handler.Response\n\t\tif err := json.Unmarshal(blw.body.Bytes(), &response); err != nil {\n\t\t\tlog.Errorf(err, \"response body can not unmarshal to model.Response struct, body: `%s`\", blw.body.Bytes())\n\t\t\tcode = errno.InternalServerError.Code\n\t\t\tmessage = err.Error()\n\t\t} else {\n\t\t\tcode = response.Code\n\t\t\tmessage = response.Message\n\t\t}\n\n\t\tlog.Infof(\"%-13s | %-12s | %s %s | {code: %d, message: %s}\", latency, ip, pad.Right(method, 5, \"\"), path, code, message)\n\t}\n}\n"
  },
  {
    "path": "demo14/router/middleware/requestid.go",
    "content": "package middleware\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/satori/go.uuid\"\n)\n\nfunc RequestId() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// Check for incoming header, use it if exists\n\t\trequestId := c.Request.Header.Get(\"X-Request-Id\")\n\n\t\t// Create request id with UUID4\n\t\tif requestId == \"\" {\n\t\t\tu4, _ := uuid.NewV4()\n\t\t\trequestId = u4.String()\n\t\t}\n\n\t\t// Expose it for use in the application\n\t\tc.Set(\"X-Request-Id\", requestId)\n\n\t\t// Set X-Request-Id header\n\t\tc.Writer.Header().Set(\"X-Request-Id\", requestId)\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "demo14/router/router.go",
    "content": "package router\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/handler/sd\"\n\t\"apiserver/handler/user\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Load loads the middlewares, routes, handlers.\nfunc Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {\n\t// Middlewares.\n\tg.Use(gin.Recovery())\n\tg.Use(middleware.NoCache)\n\tg.Use(middleware.Options)\n\tg.Use(middleware.Secure)\n\tg.Use(mw...)\n\t// 404 Handler.\n\tg.NoRoute(func(c *gin.Context) {\n\t\tc.String(http.StatusNotFound, \"The incorrect API route.\")\n\t})\n\n\t// api for authentication functionalities\n\tg.POST(\"/login\", user.Login)\n\n\t// The user handlers, requiring authentication\n\tu := g.Group(\"/v1/user\")\n\tu.Use(middleware.AuthMiddleware())\n\t{\n\t\tu.POST(\"\", user.Create)\n\t\tu.DELETE(\"/:id\", user.Delete)\n\t\tu.PUT(\"/:id\", user.Update)\n\t\tu.GET(\"\", user.List)\n\t\tu.GET(\"/:username\", user.Get)\n\t}\n\n\t// The health check handlers\n\tsvcd := g.Group(\"/sd\")\n\t{\n\t\tsvcd.GET(\"/health\", sd.HealthCheck)\n\t\tsvcd.GET(\"/disk\", sd.DiskCheck)\n\t\tsvcd.GET(\"/cpu\", sd.CPUCheck)\n\t\tsvcd.GET(\"/ram\", sd.RAMCheck)\n\t}\n\n\treturn g\n}\n"
  },
  {
    "path": "demo14/service/service.go",
    "content": "package service\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\n\t\"apiserver/model\"\n\t\"apiserver/util\"\n)\n\nfunc ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) {\n\tinfos := make([]*model.UserInfo, 0)\n\tusers, count, err := model.ListUser(username, offset, limit)\n\tif err != nil {\n\t\treturn nil, count, err\n\t}\n\n\tids := []uint64{}\n\tfor _, user := range users {\n\t\tids = append(ids, user.Id)\n\t}\n\n\twg := sync.WaitGroup{}\n\tuserList := model.UserList{\n\t\tLock:  new(sync.Mutex),\n\t\tIdMap: make(map[uint64]*model.UserInfo, len(users)),\n\t}\n\n\terrChan := make(chan error, 1)\n\tfinished := make(chan bool, 1)\n\n\t// Improve query efficiency in parallel\n\tfor _, u := range users {\n\t\twg.Add(1)\n\t\tgo func(u *model.UserModel) {\n\t\t\tdefer wg.Done()\n\n\t\t\tshortId, err := util.GenShortId()\n\t\t\tif err != nil {\n\t\t\t\terrChan <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tuserList.Lock.Lock()\n\t\t\tdefer userList.Lock.Unlock()\n\t\t\tuserList.IdMap[u.Id] = &model.UserInfo{\n\t\t\t\tId:        u.Id,\n\t\t\t\tUsername:  u.Username,\n\t\t\t\tSayHello:  fmt.Sprintf(\"Hello %s\", shortId),\n\t\t\t\tPassword:  u.Password,\n\t\t\t\tCreatedAt: u.CreatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t\tUpdatedAt: u.UpdatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t}\n\t\t}(u)\n\t}\n\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(finished)\n\t}()\n\n\tselect {\n\tcase <-finished:\n\tcase err := <-errChan:\n\t\treturn nil, count, err\n\t}\n\n\tfor _, id := range ids {\n\t\tinfos = append(infos, userList.IdMap[id])\n\t}\n\n\treturn infos, count, nil\n}\n"
  },
  {
    "path": "demo14/util/util.go",
    "content": "package util\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/teris-io/shortid\"\n)\n\nfunc GenShortId() (string, error) {\n\treturn shortid.Generate()\n}\n\nfunc GetReqID(c *gin.Context) string {\n\tv, ok := c.Get(\"X-Request-Id\")\n\tif !ok {\n\t\treturn \"\"\n\t}\n\tif requestId, ok := v.(string); ok {\n\t\treturn requestId\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "demo15/Makefile",
    "content": "SHELL := /bin/bash\nBASEDIR = $(shell pwd)\n\n# build with verison infos\nversionDir = \"apiserver/pkg/version\"\ngitTag = $(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)\nbuildDate = $(shell TZ=Asia/Shanghai date +%FT%T%z)\ngitCommit = $(shell git log --pretty=format:'%H' -n 1)\ngitTreeState = $(shell if git status|grep -q 'clean';then echo clean; else echo dirty; fi)\n\nldflags=\"-w -X ${versionDir}.gitTag=${gitTag} -X ${versionDir}.buildDate=${buildDate} -X ${versionDir}.gitCommit=${gitCommit} -X ${versionDir}.gitTreeState=${gitTreeState}\"\n\nall: gotool\n\t@go build -v -ldflags ${ldflags} .\nclean:\n\trm -f apiserver\n\tfind . -name \"[._]*.s[a-w][a-z]\" | xargs -i rm -f {}\ngotool:\n\tgofmt -w .\n\tgo tool vet . |& grep -v vendor;true\nca:\n\topenssl 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\"\n\nhelp:\n\t@echo \"make - compile the source code\"\n\t@echo \"make clean - remove binary file and vim swp files\"\n\t@echo \"make gotool - run go tool 'fmt' and 'vet'\"\n\t@echo \"make ca - generate ca files\"\n\n.PHONY: clean gotool ca help\n\n\n"
  },
  {
    "path": "demo15/README.md",
    "content": "进阶：go test测试你的代码\n"
  },
  {
    "path": "demo15/admin.sh",
    "content": "#!/bin/bash\n\nSERVER=\"apiserver\"\nBASE_DIR=$PWD\nINTERVAL=2\n\n# 命令行参数，需要手动指定\nARGS=\"\"\n\nfunction start()\n{\n\tif [ \"`pgrep $SERVER -u $UID`\" != \"\" ];then\n\t\techo \"$SERVER already running\"\n\t\texit 1\n\tfi\n\n\tnohup $BASE_DIR/$SERVER $ARGS  server &>/dev/null &\n\n\techo \"sleeping...\" &&  sleep $INTERVAL\n\n\t# check status\n\tif [ \"`pgrep $SERVER -u $UID`\" == \"\" ];then\n\t\techo \"$SERVER start failed\"\n\t\texit 1\n\tfi\n}\n\nfunction status() \n{\n\tif [ \"`pgrep $SERVER -u $UID`\" != \"\" ];then\n\t\techo $SERVER is running\n\telse\n\t\techo $SERVER is not running\n\tfi\n}\n\nfunction stop() \n{\n\tif [ \"`pgrep $SERVER -u $UID`\" != \"\" ];then\n\t\tkill -9 `pgrep $SERVER -u $UID`\n\tfi\n\n\techo \"sleeping...\" &&  sleep $INTERVAL\n\n\tif [ \"`pgrep $SERVER -u $UID`\" != \"\" ];then\n\t\techo \"$SERVER stop failed\"\n\t\texit 1\n\tfi\n}\n\ncase \"$1\" in\n\t'start')\n\tstart\n\t;;  \n\t'stop')\n\tstop\n\t;;  \n\t'status')\n\tstatus\n\t;;  \n\t'restart')\n\tstop && start\n\t;;  \n\t*)  \n\techo \"usage: $0 {start|stop|restart|status}\"\n\texit 1\n\t;;  \nesac\n"
  },
  {
    "path": "demo15/conf/config.yaml",
    "content": "runmode: debug                 # 开发模式, debug, release, test\naddr: :8080                  # HTTP绑定端口\nname: apiserver              # API Server的名字\nurl: http://127.0.0.1:8080   # pingServer函数请求的API服务器的ip:port\nmax_ping_count: 10           # pingServer函数try的次数\njwt_secret: Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5\ntls:\n  addr: :8081\n  cert: conf/server.crt\n  key: conf/server.key\nlog:\n  writers: stdout\n  logger_level: DEBUG\n  logger_file: log/apiserver.log\n  log_format_text: true\n  rollingPolicy: size\n  log_rotate_date: 1\n  log_rotate_size: 1\n  log_backup_count: 7\ndb:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\ndocker_db:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\n"
  },
  {
    "path": "demo15/conf/server.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIID2TCCAsGgAwIBAgIJAI/8axTNy37vMA0GCSqGSIb3DQEBBQUAMIGCMQswCQYD\nVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO\nUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAkxMjcuMC4wLjEx\nGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNvbTAeFw0xODA2MDQwNjMwMzFaFw0y\nODA2MDEwNjMwMzFaMIGCMQswCQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYD\nVQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklU\nMRIwEAYDVQQDDAkxMjcuMC4wLjExGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNv\nbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKHbnpmaxHokFC8rnJlG\n42eL4C3tog7rffv96DrwDajdFNk8DeaNUpZ+R4X4YPecumsCsbol2dDC1IHHGJUN\nq/A1vRjyDptMLmzp30p1aqVPcz+j8GMXtZUflLgup+HeKZRdrsk/0SaH1C3ywUBm\nyDMNHUxEvoFj0x9aJoPutnkEk9miZt/kF2vxlTbKFM14/TecVr3ljNwTebJAqEw9\niaZpNXn/IQV2I2PTGlxjyT6XNsCchjMTJ9y/XS9rBeX6BFHgQsV52CW1mWz8FieM\n/nyF9hE/TZKCpMtHWEYIdBbrxxY0BlhMxEtyIFzTha5zltFrQJGUYz7RIlyuabjk\nor8CAwEAAaNQME4wHQYDVR0OBBYEFIjLBQlToqUDoyOLeUd6HeaqiiLJMB8GA1Ud\nIwQYMBaAFIjLBQlToqUDoyOLeUd6HeaqiiLJMAwGA1UdEwQFMAMBAf8wDQYJKoZI\nhvcNAQEFBQADggEBAHAqpqGNUNgYCr3JvrVULDqFKmqLpvDKfLd6kmtnFzvuZLSz\nANE2RXXqQ5Ms3F0oHqZQq4aOvgvfas5dBLoToPL/ip+GibQ7O/1PsPD5bZaczUel\nAnmClX4ewcOEWmZx9mbYOz2C8RREvKU3MoBERrWA33LquYH0+8W8WHyMKNNF97oD\nBgWZ8R1t3N59ySZyHcye7ddlJUubRbdyCGrHbmziHV3Cj61NJiwS2JRvhYkc71M6\n8921j63qEM6i0NusFYG/w0GM3x/OeaeY/om6x+IOhJp3vIa1JPSekVyNSCwDEc3B\n+8WmVFaeMMXwErSkuu8nCbqRP8J1jiKgzpG/92g=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "demo15/conf/server.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCh256ZmsR6JBQv\nK5yZRuNni+At7aIO6337/eg68A2o3RTZPA3mjVKWfkeF+GD3nLprArG6JdnQwtSB\nxxiVDavwNb0Y8g6bTC5s6d9KdWqlT3M/o/BjF7WVH5S4Lqfh3imUXa7JP9Emh9Qt\n8sFAZsgzDR1MRL6BY9MfWiaD7rZ5BJPZombf5Bdr8ZU2yhTNeP03nFa95YzcE3my\nQKhMPYmmaTV5/yEFdiNj0xpcY8k+lzbAnIYzEyfcv10vawXl+gRR4ELFedgltZls\n/BYnjP58hfYRP02SgqTLR1hGCHQW68cWNAZYTMRLciBc04Wuc5bRa0CRlGM+0SJc\nrmm45KK/AgMBAAECggEASNMWvgf7pPT8u+iEchaKFLnDqQaFZu8f5TRtu67shnDK\ng59YpcYqRZoVtjp17pLu8Vzp+FY1dY9jq+yXq+DV3qNfLI0kc01IiiqEE+1WiYCA\n2z541y0Av1LRSDl9wcuCq8Wm8der1AlDN1VFDCPyqb2Z1AoOKQtwH2ghcjUCltn4\nNxuMYOVdql+Idl/7PCEha9pW3DQvPqJVXRPvBh6tB7FFuUOIq4+EFRUWWZlyCJfQ\nfYxB9jcFUWmAUieV3iBajg6H6QVkj/STLSgKuSktEBM2mqyL4slHXFWRt7+NQikg\ntOEWnqcTC4Ei2Dyam4SBXHJ1wT8AJV4kMTrN1ZBRmQKBgQDOMcIGHyuBI1aHcvPW\nTkein7bslFAKsA1LBqnw+alWzSHMKEmfWbBjeW0FQTIGEYMXwIq5JhnNcS5Kb5Qt\nBZgbBPDb7S80DtJnkno3iJ6hpijE3X/29lezigIuGibO5MD0Na1am2BP0iU77ifV\nxpYvj/j/pkdcTLM7hRDImSeNIwKBgQDI9EUFkGi6H9NZEles+MCHenVRYUBT2PXe\nhmtd9d5f4v3CmRsXvl4Dl/slIrJ9kuZktAck3Ocx6D0hnZcj1mwq077K7H3QqKCl\nzRwB11H5v/imsZ1ey0hLVPhCzr2miRPM/9oL5B2st9eotzhyRcrMn+oYZSSt79S8\nQ3YDiZ7TtQKBgENSr7z79GJnvVrgR4kTagRJDZrVGgVDUjPK6zXI7mdu9rgH93HW\nAOeZv+TVUpX0pc7diO3G6OnRKIIZSFIi33UC+fl0ydK/fCdhBhKXwuOYsvsEL0Hd\nUOlICEoxM7adrfqOhBlvXdTyEkItEkiUXHkPEwe1rNsQF/05By/YAbftAoGAYB2F\njd2+WZezTN0bFl58J9CIoH31eKVDJEYCwJRC4nX9jcARV0/0Q5/DvcVUvf8vN2ds\nK1OFOTetVZC8o6WBYxKYJRLsMosVG3h5NuA4E06grYoyjQ6J644emEWuLCNQVzLg\npeNb1iqweb/4vZ9oGms6WqS14IPfqpRRs+t1DikCgYEAyFtQKlLmNm/EirOMqjS2\n4/zMRj4B6n8BB8ZJsVC/MkuD+4B03rY44pP324cJnqVIcug9Zoijffnh1qbtZEc/\nMhNCa4a2MY0qcd4r/43cGbjqv94En7e5TgkH/79W+lbR3uBayt33RuuKStauZkf/\n2eBi4LlGO5stoeD63h9Ten0=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "demo15/config/config.go",
    "content": "package config\n\nimport (\n\t\"strings\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n)\n\ntype Config struct {\n\tName string\n}\n\nfunc Init(cfg string) error {\n\tc := Config{\n\t\tName: cfg,\n\t}\n\n\t// 初始化配置文件\n\tif err := c.initConfig(); err != nil {\n\t\treturn err\n\t}\n\n\t// 初始化日志包\n\tc.initLog()\n\n\t// 监控配置文件变化并热加载程序\n\tc.watchConfig()\n\n\treturn nil\n}\n\nfunc (c *Config) initConfig() error {\n\tif c.Name != \"\" {\n\t\tviper.SetConfigFile(c.Name) // 如果指定了配置文件，则解析指定的配置文件\n\t} else {\n\t\tviper.AddConfigPath(\"conf\") // 如果没有指定配置文件，则解析默认的配置文件\n\t\tviper.SetConfigName(\"config\")\n\t}\n\tviper.SetConfigType(\"yaml\")     // 设置配置文件格式为YAML\n\tviper.AutomaticEnv()            // 读取匹配的环境变量\n\tviper.SetEnvPrefix(\"APISERVER\") // 读取环境变量的前缀为APISERVER\n\treplacer := strings.NewReplacer(\".\", \"_\")\n\tviper.SetEnvKeyReplacer(replacer)\n\tif err := viper.ReadInConfig(); err != nil { // viper解析配置文件\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *Config) initLog() {\n\tpassLagerCfg := log.PassLagerCfg{\n\t\tWriters:        viper.GetString(\"log.writers\"),\n\t\tLoggerLevel:    viper.GetString(\"log.logger_level\"),\n\t\tLoggerFile:     viper.GetString(\"log.logger_file\"),\n\t\tLogFormatText:  viper.GetBool(\"log.log_format_text\"),\n\t\tRollingPolicy:  viper.GetString(\"log.rollingPolicy\"),\n\t\tLogRotateDate:  viper.GetInt(\"log.log_rotate_date\"),\n\t\tLogRotateSize:  viper.GetInt(\"log.log_rotate_size\"),\n\t\tLogBackupCount: viper.GetInt(\"log.log_backup_count\"),\n\t}\n\n\tlog.InitWithConfig(&passLagerCfg)\n}\n\n// 监控配置文件变化并热加载程序\nfunc (c *Config) watchConfig() {\n\tviper.WatchConfig()\n\tviper.OnConfigChange(func(e fsnotify.Event) {\n\t\tlog.Infof(\"Config file changed: %s\", e.Name)\n\t})\n}\n"
  },
  {
    "path": "demo15/db.sql",
    "content": "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n/*!40101 SET NAMES utf8 */;\n/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;\n/*!40103 SET TIME_ZONE='+00:00' */;\n/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;\n/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n\nCREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */;\n\nUSE `db_apiserver`;\n\n--\n-- Table structure for table `tb_users`\n--\n\nDROP TABLE IF EXISTS `tb_users`;\n/*!40101 SET @saved_cs_client     = @@character_set_client */;\n/*!40101 SET character_set_client = utf8 */;\nCREATE TABLE `tb_users` (\n  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n  `username` varchar(255) NOT NULL,\n  `password` varchar(255) NOT NULL,\n  `createdAt` timestamp NULL DEFAULT NULL,\n  `updatedAt` timestamp NULL DEFAULT NULL,\n  `deletedAt` timestamp NULL DEFAULT NULL,\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `username` (`username`),\n  KEY `idx_tb_users_deletedAt` (`deletedAt`)\n) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;\n/*!40101 SET character_set_client = @saved_cs_client */;\n\n--\n-- Dumping data for table `tb_users`\n--\n\nLOCK TABLES `tb_users` WRITE;\n/*!40000 ALTER TABLE `tb_users` DISABLE KEYS */;\nINSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL);\n/*!40000 ALTER TABLE `tb_users` ENABLE KEYS */;\nUNLOCK TABLES;\n/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;\n\n/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;\n/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;\n/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n\n-- Dump completed on 2018-05-28  0:25:41\n"
  },
  {
    "path": "demo15/handler/handler.go",
    "content": "package handler\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype Response struct {\n\tCode    int         `json:\"code\"`\n\tMessage string      `json:\"message\"`\n\tData    interface{} `json:\"data\"`\n}\n\nfunc SendResponse(c *gin.Context, err error, data interface{}) {\n\tcode, message := errno.DecodeErr(err)\n\n\t// always return http.StatusOK\n\tc.JSON(http.StatusOK, Response{\n\t\tCode:    code,\n\t\tMessage: message,\n\t\tData:    data,\n\t})\n}\n"
  },
  {
    "path": "demo15/handler/sd/check.go",
    "content": "package sd\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/shirou/gopsutil/cpu\"\n\t\"github.com/shirou/gopsutil/disk\"\n\t\"github.com/shirou/gopsutil/load\"\n\t\"github.com/shirou/gopsutil/mem\"\n)\n\nconst (\n\tB  = 1\n\tKB = 1024 * B\n\tMB = 1024 * KB\n\tGB = 1024 * MB\n)\n\n// HealthCheck shows `OK` as the ping-pong result.\nfunc HealthCheck(c *gin.Context) {\n\tmessage := \"OK\"\n\tc.String(http.StatusOK, \"\\n\"+message)\n}\n\n// DiskCheck checks the disk usage.\nfunc DiskCheck(c *gin.Context) {\n\tu, _ := disk.Usage(\"/\")\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusOK\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n\n// CPUCheck checks the cpu usage.\nfunc CPUCheck(c *gin.Context) {\n\tcores, _ := cpu.Counts(false)\n\n\ta, _ := load.Avg()\n\tl1 := a.Load1\n\tl5 := a.Load5\n\tl15 := a.Load15\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif l5 >= float64(cores-1) {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if l5 >= float64(cores-2) {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Load average: %.2f, %.2f, %.2f | Cores: %d\", text, l1, l5, l15, cores)\n\tc.String(status, \"\\n\"+message)\n}\n\n// RAMCheck checks the disk usage.\nfunc RAMCheck(c *gin.Context) {\n\tu, _ := mem.VirtualMemory()\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n"
  },
  {
    "path": "demo15/handler/user/create.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// Create creates a new user account.\nfunc Create(c *gin.Context) {\n\tlog.Info(\"User Create function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\tvar r CreateRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tu := model.UserModel{\n\t\tUsername: r.Username,\n\t\tPassword: r.Password,\n\t}\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\t// Insert the user to the database.\n\tif err := u.Create(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\trsp := CreateResponse{\n\t\tUsername: r.Username,\n\t}\n\n\t// Show the user information.\n\tSendResponse(c, nil, rsp)\n}\n"
  },
  {
    "path": "demo15/handler/user/delete.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Delete delete an user by the user identifier.\nfunc Delete(c *gin.Context) {\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\tif err := model.DeleteUser(uint64(userId)); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo15/handler/user/get.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Get gets an user by the user identifier.\nfunc Get(c *gin.Context) {\n\tusername := c.Param(\"username\")\n\t// Get the user by the `username` from the database.\n\tuser, err := model.GetUser(username)\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrUserNotFound, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, user)\n}\n"
  },
  {
    "path": "demo15/handler/user/list.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/service\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n)\n\n// List list the users in the database.\nfunc List(c *gin.Context) {\n\tlog.Info(\"List function called.\")\n\tvar r ListRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tinfos, count, err := service.ListUser(r.Username, r.Offset, r.Limit)\n\tif err != nil {\n\t\tSendResponse(c, err, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, ListResponse{\n\t\tTotalCount: count,\n\t\tUserList:   infos,\n\t})\n}\n"
  },
  {
    "path": "demo15/handler/user/login.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/auth\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/pkg/token\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Login generates the authentication token\n// if the password was matched with the specified account.\nfunc Login(c *gin.Context) {\n\t// Binding the data with the user struct.\n\tvar u model.UserModel\n\tif err := c.Bind(&u); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\t// Get the user information by the login username.\n\td, err := model.GetUser(u.Username)\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrUserNotFound, nil)\n\t\treturn\n\t}\n\n\t// Compare the login password with the user password.\n\tif err := auth.Compare(d.Password, u.Password); err != nil {\n\t\tSendResponse(c, errno.ErrPasswordIncorrect, nil)\n\t\treturn\n\t}\n\n\t// Sign the json web token.\n\tt, err := token.Sign(c, token.Context{ID: d.Id, Username: d.Username}, \"\")\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrToken, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, model.Token{Token: t})\n}\n"
  },
  {
    "path": "demo15/handler/user/update.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// Update update a exist user account info.\nfunc Update(c *gin.Context) {\n\tlog.Info(\"Update function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\t// Get the user id from the url parameter.\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\n\t// Binding the user data.\n\tvar u model.UserModel\n\tif err := c.Bind(&u); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\t// We update the record based on the user id.\n\tu.Id = uint64(userId)\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\n\t// Save changed fields.\n\tif err := u.Update(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo15/handler/user/user.go",
    "content": "package user\n\nimport (\n\t\"apiserver/model\"\n)\n\ntype CreateRequest struct {\n\tUsername string `json:\"username\"`\n\tPassword string `json:\"password\"`\n}\n\ntype CreateResponse struct {\n\tUsername string `json:\"username\"`\n}\n\ntype ListRequest struct {\n\tUsername string `json:\"username\"`\n\tOffset   int    `json:\"offset\"`\n\tLimit    int    `json:\"limit\"`\n}\n\ntype ListResponse struct {\n\tTotalCount uint64            `json:\"totalCount\"`\n\tUserList   []*model.UserInfo `json:\"userList\"`\n}\n"
  },
  {
    "path": "demo15/main.go",
    "content": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n\n\t\"apiserver/config\"\n\t\"apiserver/model\"\n\tv \"apiserver/pkg/version\"\n\t\"apiserver/router\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tcfg     = pflag.StringP(\"config\", \"c\", \"\", \"apiserver config file path.\")\n\tversion = pflag.BoolP(\"version\", \"v\", false, \"show version info.\")\n)\n\nfunc main() {\n\tpflag.Parse()\n\tif *version {\n\t\tv := v.Get()\n\t\tmarshalled, err := json.MarshalIndent(&v, \"\", \"  \")\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"%v\\n\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tfmt.Println(string(marshalled))\n\t\treturn\n\t}\n\n\t// init config\n\tif err := config.Init(*cfg); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// init db\n\tmodel.DB.Init()\n\tdefer model.DB.Close()\n\n\t// Set gin mode.\n\tgin.SetMode(viper.GetString(\"runmode\"))\n\n\t// Create the Gin engine.\n\tg := gin.New()\n\n\t// Routes.\n\trouter.Load(\n\t\t// Cores.\n\t\tg,\n\n\t\t// Middlwares.\n\t\tmiddleware.Logging(),\n\t\tmiddleware.RequestId(),\n\t)\n\n\t// Ping the server to make sure the router is working.\n\tgo func() {\n\t\tif err := pingServer(); err != nil {\n\t\t\tlog.Fatal(\"The router has no response, or it might took too long to start up.\", err)\n\t\t}\n\t\tlog.Info(\"The router has been deployed successfully.\")\n\t}()\n\n\t// Start to listening the incoming requests.\n\tcert := viper.GetString(\"tls.cert\")\n\tkey := viper.GetString(\"tls.key\")\n\tif cert != \"\" && key != \"\" {\n\t\tgo func() {\n\t\t\tlog.Infof(\"Start to listening the incoming requests on https address: %s\", viper.GetString(\"tls.addr\"))\n\t\t\tlog.Info(http.ListenAndServeTLS(viper.GetString(\"tls.addr\"), cert, key, g).Error())\n\t\t}()\n\t}\n\n\tlog.Infof(\"Start to listening the incoming requests on http address: %s\", viper.GetString(\"addr\"))\n\tlog.Info(http.ListenAndServe(viper.GetString(\"addr\"), g).Error())\n}\n\n// pingServer pings the http server to make sure the router is working.\nfunc pingServer() error {\n\tfor i := 0; i < viper.GetInt(\"max_ping_count\"); i++ {\n\t\t// Ping the server by sending a GET request to `/health`.\n\t\tresp, err := http.Get(viper.GetString(\"url\") + \"/sd/health\")\n\t\tif err == nil && resp.StatusCode == 200 {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Sleep for a second to continue the next ping.\n\t\tlog.Info(\"Waiting for the router, retry in 1 second.\")\n\t\ttime.Sleep(time.Second)\n\t}\n\treturn errors.New(\"Cannot connect to the router.\")\n}\n"
  },
  {
    "path": "demo15/model/init.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n\t// MySQL driver.\n\t\"github.com/jinzhu/gorm\"\n\t_ \"github.com/jinzhu/gorm/dialects/mysql\"\n)\n\ntype Database struct {\n\tSelf   *gorm.DB\n\tDocker *gorm.DB\n}\n\nvar DB *Database\n\nfunc openDB(username, password, addr, name string) *gorm.DB {\n\tconfig := fmt.Sprintf(\"%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s\",\n\t\tusername,\n\t\tpassword,\n\t\taddr,\n\t\tname,\n\t\ttrue,\n\t\t//\"Asia/Shanghai\"),\n\t\t\"Local\")\n\n\tdb, err := gorm.Open(\"mysql\", config)\n\tif err != nil {\n\t\tlog.Errorf(err, \"Database connection failed. Database name: %s\", name)\n\t}\n\n\t// set for db connection\n\tsetupDB(db)\n\n\treturn db\n}\n\nfunc setupDB(db *gorm.DB) {\n\tdb.LogMode(viper.GetBool(\"gormlog\"))\n\t//db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数，默认值为0表示不限制.设置最大的连接数，可以避免并发太高导致连接mysql出现too many connections的错误。\n\tdb.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。\n}\n\n// used for cli\nfunc InitSelfDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"db.username\"),\n\t\tviper.GetString(\"db.password\"),\n\t\tviper.GetString(\"db.addr\"),\n\t\tviper.GetString(\"db.name\"))\n}\n\nfunc GetSelfDB() *gorm.DB {\n\treturn InitSelfDB()\n}\n\nfunc InitDockerDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"docker_db.username\"),\n\t\tviper.GetString(\"docker_db.password\"),\n\t\tviper.GetString(\"docker_db.addr\"),\n\t\tviper.GetString(\"docker_db.name\"))\n}\n\nfunc GetDockerDB() *gorm.DB {\n\treturn InitDockerDB()\n}\n\nfunc (db *Database) Init() {\n\tDB = &Database{\n\t\tSelf:   GetSelfDB(),\n\t\tDocker: GetDockerDB(),\n\t}\n}\n\nfunc (db *Database) Close() {\n\tDB.Self.Close()\n\tDB.Docker.Close()\n}\n"
  },
  {
    "path": "demo15/model/model.go",
    "content": "package model\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype BaseModel struct {\n\tId        uint64     `gorm:\"primary_key;AUTO_INCREMENT;column:id\" json:\"-\"`\n\tCreatedAt time.Time  `gorm:\"column:createdAt\" json:\"-\"`\n\tUpdatedAt time.Time  `gorm:\"column:updatedAt\" json:\"-\"`\n\tDeletedAt *time.Time `gorm:\"column:deletedAt\" sql:\"index\" json:\"-\"`\n}\n\ntype UserInfo struct {\n\tId        uint64 `json:\"id\"`\n\tUsername  string `json:\"username\"`\n\tSayHello  string `json:\"sayHello\"`\n\tPassword  string `json:\"password\"`\n\tCreatedAt string `json:\"createdAt\"`\n\tUpdatedAt string `json:\"updatedAt\"`\n}\n\ntype UserList struct {\n\tLock  *sync.Mutex\n\tIdMap map[uint64]*UserInfo\n}\n\n// Token represents a JSON web token.\ntype Token struct {\n\tToken string `json:\"token\"`\n}\n"
  },
  {
    "path": "demo15/model/user.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"apiserver/pkg/auth\"\n\t\"apiserver/pkg/constvar\"\n\n\tvalidator \"gopkg.in/go-playground/validator.v9\"\n)\n\n// User represents a registered user.\ntype UserModel struct {\n\tBaseModel\n\tUsername string `json:\"username\" gorm:\"column:username;not null\" binding:\"required\" validate:\"min=1,max=32\"`\n\tPassword string `json:\"password\" gorm:\"column:password;not null\" binding:\"required\" validate:\"min=5,max=128\"`\n}\n\nfunc (c *UserModel) TableName() string {\n\treturn \"tb_users\"\n}\n\n// Create creates a new user account.\nfunc (u *UserModel) Create() error {\n\treturn DB.Self.Create(&u).Error\n}\n\n// DeleteUser deletes the user by the user identifier.\nfunc DeleteUser(id uint64) error {\n\tuser := UserModel{}\n\tuser.BaseModel.Id = id\n\treturn DB.Self.Delete(&user).Error\n}\n\n// Update updates an user account information.\nfunc (u *UserModel) Update() error {\n\treturn DB.Self.Save(u).Error\n}\n\n// GetUser gets an user by the user identifier.\nfunc GetUser(username string) (*UserModel, error) {\n\tu := &UserModel{}\n\td := DB.Self.Where(\"username = ?\", username).First(&u)\n\treturn u, d.Error\n}\n\n// ListUser List all users\nfunc ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) {\n\tif limit == 0 {\n\t\tlimit = constvar.DefaultLimit\n\t}\n\n\tusers := make([]*UserModel, 0)\n\tvar count uint64\n\n\twhere := fmt.Sprintf(\"username like '%%%s%%'\", username)\n\tif err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\tif err := DB.Self.Where(where).Offset(offset).Limit(limit).Order(\"id desc\").Find(&users).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\treturn users, count, nil\n}\n\n// Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct).\nfunc (u *UserModel) Compare(pwd string) (err error) {\n\terr = auth.Compare(u.Password, pwd)\n\treturn\n}\n\n// Encrypt the user password.\nfunc (u *UserModel) Encrypt() (err error) {\n\tu.Password, err = auth.Encrypt(u.Password)\n\treturn\n}\n\n// Validate the fields.\nfunc (u *UserModel) Validate() error {\n\tvalidate := validator.New()\n\treturn validate.Struct(u)\n}\n"
  },
  {
    "path": "demo15/pkg/auth/auth.go",
    "content": "package auth\n\nimport \"golang.org/x/crypto/bcrypt\"\n\n// Encrypt encrypts the plain text with bcrypt.\nfunc Encrypt(source string) (string, error) {\n\thashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost)\n\treturn string(hashedBytes), err\n}\n\n// Compare compares the encrypted text with the plain text if it's the same.\nfunc Compare(hashedPassword, password string) error {\n\treturn bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))\n}\n"
  },
  {
    "path": "demo15/pkg/constvar/constvar.go",
    "content": "package constvar\n\nconst (\n\tDefaultLimit = 50\n)\n"
  },
  {
    "path": "demo15/pkg/errno/code.go",
    "content": "package errno\n\nvar (\n\t// Common errors\n\tOK                  = &Errno{Code: 0, Message: \"OK\"}\n\tInternalServerError = &Errno{Code: 10001, Message: \"Internal server error\"}\n\tErrBind             = &Errno{Code: 10002, Message: \"Error occurred while binding the request body to the struct.\"}\n\n\tErrValidation = &Errno{Code: 20001, Message: \"Validation failed.\"}\n\tErrDatabase   = &Errno{Code: 20002, Message: \"Database error.\"}\n\tErrToken      = &Errno{Code: 20003, Message: \"Error occurred while signing the JSON web token.\"}\n\n\t// user errors\n\tErrEncrypt           = &Errno{Code: 20101, Message: \"Error occurred while encrypting the user password.\"}\n\tErrUserNotFound      = &Errno{Code: 20102, Message: \"The user was not found.\"}\n\tErrTokenInvalid      = &Errno{Code: 20103, Message: \"The token was invalid.\"}\n\tErrPasswordIncorrect = &Errno{Code: 20104, Message: \"The password was incorrect.\"}\n)\n"
  },
  {
    "path": "demo15/pkg/errno/errno.go",
    "content": "package errno\n\nimport \"fmt\"\n\ntype Errno struct {\n\tCode    int\n\tMessage string\n}\n\nfunc (err Errno) Error() string {\n\treturn err.Message\n}\n\n// Err represents an error\ntype Err struct {\n\tCode    int\n\tMessage string\n\tErr     error\n}\n\nfunc New(errno *Errno, err error) *Err {\n\treturn &Err{Code: errno.Code, Message: errno.Message, Err: err}\n}\n\nfunc (err *Err) Add(message string) error {\n\terr.Message += \" \" + message\n\treturn err\n}\n\nfunc (err *Err) Addf(format string, args ...interface{}) error {\n\terr.Message += \" \" + fmt.Sprintf(format, args...)\n\treturn err\n}\n\nfunc (err *Err) Error() string {\n\treturn fmt.Sprintf(\"Err - code: %d, message: %s, error: %s\", err.Code, err.Message, err.Err)\n}\n\nfunc IsErrUserNotFound(err error) bool {\n\tcode, _ := DecodeErr(err)\n\treturn code == ErrUserNotFound.Code\n}\n\nfunc DecodeErr(err error) (int, string) {\n\tif err == nil {\n\t\treturn OK.Code, OK.Message\n\t}\n\n\tswitch typed := err.(type) {\n\tcase *Err:\n\t\treturn typed.Code, typed.Message\n\tcase *Errno:\n\t\treturn typed.Code, typed.Message\n\tdefault:\n\t}\n\n\treturn InternalServerError.Code, err.Error()\n}\n"
  },
  {
    "path": "demo15/pkg/token/token.go",
    "content": "package token\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\tjwt \"github.com/dgrijalva/jwt-go\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\t// ErrMissingHeader means the `Authorization` header was empty.\n\tErrMissingHeader = errors.New(\"The length of the `Authorization` header is zero.\")\n)\n\n// Context is the context of the JSON web token.\ntype Context struct {\n\tID       uint64\n\tUsername string\n}\n\n// secretFunc validates the secret format.\nfunc secretFunc(secret string) jwt.Keyfunc {\n\treturn func(token *jwt.Token) (interface{}, error) {\n\t\t// Make sure the `alg` is what we except.\n\t\tif _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {\n\t\t\treturn nil, jwt.ErrSignatureInvalid\n\t\t}\n\n\t\treturn []byte(secret), nil\n\t}\n}\n\n// Parse validates the token with the specified secret,\n// and returns the context if the token was valid.\nfunc Parse(tokenString string, secret string) (*Context, error) {\n\tctx := &Context{}\n\n\t// Parse the token.\n\ttoken, err := jwt.Parse(tokenString, secretFunc(secret))\n\n\t// Parse error.\n\tif err != nil {\n\t\treturn ctx, err\n\n\t\t// Read the token if it's valid.\n\t} else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {\n\t\tctx.ID = uint64(claims[\"id\"].(float64))\n\t\tctx.Username = claims[\"username\"].(string)\n\t\treturn ctx, nil\n\n\t\t// Other errors.\n\t} else {\n\t\treturn ctx, err\n\t}\n}\n\n// ParseRequest gets the token from the header and\n// pass it to the Parse function to parses the token.\nfunc ParseRequest(c *gin.Context) (*Context, error) {\n\theader := c.Request.Header.Get(\"Authorization\")\n\n\t// Load the jwt secret from config\n\tsecret := viper.GetString(\"jwt_secret\")\n\n\tif len(header) == 0 {\n\t\treturn &Context{}, ErrMissingHeader\n\t}\n\n\tvar t string\n\t// Parse the header to get the token part.\n\tfmt.Sscanf(header, \"Bearer %s\", &t)\n\treturn Parse(t, secret)\n}\n\n// Sign signs the context with the specified secret.\nfunc Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) {\n\t// Load the jwt secret from the Gin config if the secret isn't specified.\n\tif secret == \"\" {\n\t\tsecret = viper.GetString(\"jwt_secret\")\n\t}\n\t// The token content.\n\ttoken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{\n\t\t\"id\":       c.ID,\n\t\t\"username\": c.Username,\n\t\t\"nbf\":      time.Now().Unix(),\n\t\t\"iat\":      time.Now().Unix(),\n\t})\n\t// Sign the token with the specified secret.\n\ttokenString, err = token.SignedString([]byte(secret))\n\n\treturn\n}\n"
  },
  {
    "path": "demo15/pkg/version/base.go",
    "content": "package version\n\nvar (\n\tgitTag       string = \"\"\n\tgitCommit    string = \"$Format:%H$\"          // sha1 from git, output of $(git rev-parse HEAD)\n\tgitTreeState string = \"not a git tree\"       // state of git tree, either \"clean\" or \"dirty\"\n\tbuildDate    string = \"1970-01-01T00:00:00Z\" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')\n)\n"
  },
  {
    "path": "demo15/pkg/version/doc.go",
    "content": "package version\n"
  },
  {
    "path": "demo15/pkg/version/version.go",
    "content": "package version\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n)\n\n// Info contains versioning information.\ntype Info struct {\n\tGitTag       string `json:\"gitTag\"`\n\tGitCommit    string `json:\"gitCommit\"`\n\tGitTreeState string `json:\"gitTreeState\"`\n\tBuildDate    string `json:\"buildDate\"`\n\tGoVersion    string `json:\"goVersion\"`\n\tCompiler     string `json:\"compiler\"`\n\tPlatform     string `json:\"platform\"`\n}\n\n// String returns info as a human-friendly version string.\nfunc (info Info) String() string {\n\treturn info.GitTag\n}\n\nfunc Get() Info {\n\treturn Info{\n\t\tGitTag:       gitTag,\n\t\tGitCommit:    gitCommit,\n\t\tGitTreeState: gitTreeState,\n\t\tBuildDate:    buildDate,\n\t\tGoVersion:    runtime.Version(),\n\t\tCompiler:     runtime.Compiler,\n\t\tPlatform:     fmt.Sprintf(\"%s/%s\", runtime.GOOS, runtime.GOARCH),\n\t}\n}\n"
  },
  {
    "path": "demo15/router/middleware/auth.go",
    "content": "package middleware\n\nimport (\n\t\"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/pkg/token\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc AuthMiddleware() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// Parse the json web token.\n\t\tif _, err := token.ParseRequest(c); err != nil {\n\t\t\thandler.SendResponse(c, errno.ErrTokenInvalid, nil)\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "demo15/router/middleware/header.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// NoCache is a middleware function that appends headers\n// to prevent the client from caching the HTTP response.\nfunc NoCache(c *gin.Context) {\n\tc.Header(\"Cache-Control\", \"no-cache, no-store, max-age=0, must-revalidate, value\")\n\tc.Header(\"Expires\", \"Thu, 01 Jan 1970 00:00:00 GMT\")\n\tc.Header(\"Last-Modified\", time.Now().UTC().Format(http.TimeFormat))\n\tc.Next()\n}\n\n// Options is a middleware function that appends headers\n// for options requests and aborts then exits the middleware\n// chain and ends the request.\nfunc Options(c *gin.Context) {\n\tif c.Request.Method != \"OPTIONS\" {\n\t\tc.Next()\n\t} else {\n\t\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\t\tc.Header(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Access-Control-Allow-Headers\", \"authorization, origin, content-type, accept\")\n\t\tc.Header(\"Allow\", \"HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Content-Type\", \"application/json\")\n\t\tc.AbortWithStatus(200)\n\t}\n}\n\n// Secure is a middleware function that appends security\n// and resource access headers.\nfunc Secure(c *gin.Context) {\n\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\tc.Header(\"X-Frame-Options\", \"DENY\")\n\tc.Header(\"X-Content-Type-Options\", \"nosniff\")\n\tc.Header(\"X-XSS-Protection\", \"1; mode=block\")\n\tif c.Request.TLS != nil {\n\t\tc.Header(\"Strict-Transport-Security\", \"max-age=31536000\")\n\t}\n\n\t// Also consider adding Content-Security-Policy headers\n\t// c.Header(\"Content-Security-Policy\", \"script-src 'self' https://cdnjs.cloudflare.com\")\n}\n"
  },
  {
    "path": "demo15/router/middleware/logging.go",
    "content": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/willf/pad\"\n)\n\ntype bodyLogWriter struct {\n\tgin.ResponseWriter\n\tbody *bytes.Buffer\n}\n\nfunc (w bodyLogWriter) Write(b []byte) (int, error) {\n\tw.body.Write(b)\n\treturn w.ResponseWriter.Write(b)\n}\n\n// Logging is a middleware function that logs the each request.\nfunc Logging() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tstart := time.Now().UTC()\n\t\tpath := c.Request.URL.Path\n\n\t\treg := regexp.MustCompile(\"(/v1/user|/login)\")\n\t\tif !reg.MatchString(path) {\n\t\t\treturn\n\t\t}\n\n\t\t// Skip for the health check requests.\n\t\tif path == \"/sd/health\" || path == \"/sd/ram\" || path == \"/sd/cpu\" || path == \"/sd/disk\" {\n\t\t\treturn\n\t\t}\n\n\t\t// Read the Body content\n\t\tvar bodyBytes []byte\n\t\tif c.Request.Body != nil {\n\t\t\tbodyBytes, _ = ioutil.ReadAll(c.Request.Body)\n\t\t}\n\n\t\t// Restore the io.ReadCloser to its original state\n\t\tc.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))\n\n\t\t// The basic informations.\n\t\tmethod := c.Request.Method\n\t\tip := c.ClientIP()\n\n\t\t//log.Debugf(\"New request come in, path: %s, Method: %s, body `%s`\", path, method, string(bodyBytes))\n\t\tblw := &bodyLogWriter{\n\t\t\tbody:           bytes.NewBufferString(\"\"),\n\t\t\tResponseWriter: c.Writer,\n\t\t}\n\t\tc.Writer = blw\n\n\t\t// Continue.\n\t\tc.Next()\n\n\t\t// Calculates the latency.\n\t\tend := time.Now().UTC()\n\t\tlatency := end.Sub(start)\n\n\t\tcode, message := -1, \"\"\n\n\t\t// get code and message\n\t\tvar response handler.Response\n\t\tif err := json.Unmarshal(blw.body.Bytes(), &response); err != nil {\n\t\t\tlog.Errorf(err, \"response body can not unmarshal to model.Response struct, body: `%s`\", blw.body.Bytes())\n\t\t\tcode = errno.InternalServerError.Code\n\t\t\tmessage = err.Error()\n\t\t} else {\n\t\t\tcode = response.Code\n\t\t\tmessage = response.Message\n\t\t}\n\n\t\tlog.Infof(\"%-13s | %-12s | %s %s | {code: %d, message: %s}\", latency, ip, pad.Right(method, 5, \"\"), path, code, message)\n\t}\n}\n"
  },
  {
    "path": "demo15/router/middleware/requestid.go",
    "content": "package middleware\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/satori/go.uuid\"\n)\n\nfunc RequestId() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// Check for incoming header, use it if exists\n\t\trequestId := c.Request.Header.Get(\"X-Request-Id\")\n\n\t\t// Create request id with UUID4\n\t\tif requestId == \"\" {\n\t\t\tu4, _ := uuid.NewV4()\n\t\t\trequestId = u4.String()\n\t\t}\n\n\t\t// Expose it for use in the application\n\t\tc.Set(\"X-Request-Id\", requestId)\n\n\t\t// Set X-Request-Id header\n\t\tc.Writer.Header().Set(\"X-Request-Id\", requestId)\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "demo15/router/router.go",
    "content": "package router\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/handler/sd\"\n\t\"apiserver/handler/user\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Load loads the middlewares, routes, handlers.\nfunc Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {\n\t// Middlewares.\n\tg.Use(gin.Recovery())\n\tg.Use(middleware.NoCache)\n\tg.Use(middleware.Options)\n\tg.Use(middleware.Secure)\n\tg.Use(mw...)\n\t// 404 Handler.\n\tg.NoRoute(func(c *gin.Context) {\n\t\tc.String(http.StatusNotFound, \"The incorrect API route.\")\n\t})\n\n\t// api for authentication functionalities\n\tg.POST(\"/login\", user.Login)\n\n\t// The user handlers, requiring authentication\n\tu := g.Group(\"/v1/user\")\n\tu.Use(middleware.AuthMiddleware())\n\t{\n\t\tu.POST(\"\", user.Create)\n\t\tu.DELETE(\"/:id\", user.Delete)\n\t\tu.PUT(\"/:id\", user.Update)\n\t\tu.GET(\"\", user.List)\n\t\tu.GET(\"/:username\", user.Get)\n\t}\n\n\t// The health check handlers\n\tsvcd := g.Group(\"/sd\")\n\t{\n\t\tsvcd.GET(\"/health\", sd.HealthCheck)\n\t\tsvcd.GET(\"/disk\", sd.DiskCheck)\n\t\tsvcd.GET(\"/cpu\", sd.CPUCheck)\n\t\tsvcd.GET(\"/ram\", sd.RAMCheck)\n\t}\n\n\treturn g\n}\n"
  },
  {
    "path": "demo15/service/service.go",
    "content": "package service\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\n\t\"apiserver/model\"\n\t\"apiserver/util\"\n)\n\nfunc ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) {\n\tinfos := make([]*model.UserInfo, 0)\n\tusers, count, err := model.ListUser(username, offset, limit)\n\tif err != nil {\n\t\treturn nil, count, err\n\t}\n\n\tids := []uint64{}\n\tfor _, user := range users {\n\t\tids = append(ids, user.Id)\n\t}\n\n\twg := sync.WaitGroup{}\n\tuserList := model.UserList{\n\t\tLock:  new(sync.Mutex),\n\t\tIdMap: make(map[uint64]*model.UserInfo, len(users)),\n\t}\n\n\terrChan := make(chan error, 1)\n\tfinished := make(chan bool, 1)\n\n\t// Improve query efficiency in parallel\n\tfor _, u := range users {\n\t\twg.Add(1)\n\t\tgo func(u *model.UserModel) {\n\t\t\tdefer wg.Done()\n\n\t\t\tshortId, err := util.GenShortId()\n\t\t\tif err != nil {\n\t\t\t\terrChan <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tuserList.Lock.Lock()\n\t\t\tdefer userList.Lock.Unlock()\n\t\t\tuserList.IdMap[u.Id] = &model.UserInfo{\n\t\t\t\tId:        u.Id,\n\t\t\t\tUsername:  u.Username,\n\t\t\t\tSayHello:  fmt.Sprintf(\"Hello %s\", shortId),\n\t\t\t\tPassword:  u.Password,\n\t\t\t\tCreatedAt: u.CreatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t\tUpdatedAt: u.UpdatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t}\n\t\t}(u)\n\t}\n\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(finished)\n\t}()\n\n\tselect {\n\tcase <-finished:\n\tcase err := <-errChan:\n\t\treturn nil, count, err\n\t}\n\n\tfor _, id := range ids {\n\t\tinfos = append(infos, userList.IdMap[id])\n\t}\n\n\treturn infos, count, nil\n}\n"
  },
  {
    "path": "demo15/util/util.go",
    "content": "package util\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/teris-io/shortid\"\n)\n\nfunc GenShortId() (string, error) {\n\treturn shortid.Generate()\n}\n\nfunc GetReqID(c *gin.Context) string {\n\tv, ok := c.Get(\"X-Request-Id\")\n\tif !ok {\n\t\treturn \"\"\n\t}\n\tif requestId, ok := v.(string); ok {\n\t\treturn requestId\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "demo15/util/util_test.go",
    "content": "package util\n\nimport (\n\t\"testing\"\n)\n\nfunc TestGenShortId(t *testing.T) {\n\tshortId, err := GenShortId()\n\tif shortId == \"\" || err != nil {\n\t\tt.Error(\"GenShortId failed!\")\n\t}\n\n\tt.Log(\"GenShortId test pass\")\n}\n\nfunc BenchmarkGenShortId(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\tGenShortId()\n\t}\n}\n\nfunc BenchmarkGenShortIdTimeConsuming(b *testing.B) {\n\tb.StopTimer() //调用该函数停止压力测试的时间计数\n\n\tshortId, err := GenShortId()\n\tif shortId == \"\" || err != nil {\n\t\tb.Error(err)\n\t}\n\n\tb.StartTimer() //重新开始时间\n\n\tfor i := 0; i < b.N; i++ {\n\t\tGenShortId()\n\t}\n}\n"
  },
  {
    "path": "demo16/Makefile",
    "content": "SHELL := /bin/bash\nBASEDIR = $(shell pwd)\n\n# build with verison infos\nversionDir = \"apiserver/pkg/version\"\ngitTag = $(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)\nbuildDate = $(shell TZ=Asia/Shanghai date +%FT%T%z)\ngitCommit = $(shell git log --pretty=format:'%H' -n 1)\ngitTreeState = $(shell if git status|grep -q 'clean';then echo clean; else echo dirty; fi)\n\nldflags=\"-w -X ${versionDir}.gitTag=${gitTag} -X ${versionDir}.buildDate=${buildDate} -X ${versionDir}.gitCommit=${gitCommit} -X ${versionDir}.gitTreeState=${gitTreeState}\"\n\nall: gotool\n\t@go build -v -ldflags ${ldflags} .\nclean:\n\trm -f apiserver\n\tfind . -name \"[._]*.s[a-w][a-z]\" | xargs -i rm -f {}\ngotool:\n\tgofmt -w .\n\tgo tool vet . |& grep -v vendor;true\nca:\n\topenssl 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\"\n\nhelp:\n\t@echo \"make - compile the source code\"\n\t@echo \"make clean - remove binary file and vim swp files\"\n\t@echo \"make gotool - run go tool 'fmt' and 'vet'\"\n\t@echo \"make ca - generate ca files\"\n\n.PHONY: clean gotool ca help\n\n\n"
  },
  {
    "path": "demo16/README.md",
    "content": "进阶：API性能分析\n"
  },
  {
    "path": "demo16/admin.sh",
    "content": "#!/bin/bash\n\nSERVER=\"apiserver\"\nBASE_DIR=$PWD\nINTERVAL=2\n\n# 命令行参数，需要手动指定\nARGS=\"\"\n\nfunction start()\n{\n\tif [ \"`pgrep $SERVER -u $UID`\" != \"\" ];then\n\t\techo \"$SERVER already running\"\n\t\texit 1\n\tfi\n\n\tnohup $BASE_DIR/$SERVER $ARGS  server &>/dev/null &\n\n\techo \"sleeping...\" &&  sleep $INTERVAL\n\n\t# check status\n\tif [ \"`pgrep $SERVER -u $UID`\" == \"\" ];then\n\t\techo \"$SERVER start failed\"\n\t\texit 1\n\tfi\n}\n\nfunction status() \n{\n\tif [ \"`pgrep $SERVER -u $UID`\" != \"\" ];then\n\t\techo $SERVER is running\n\telse\n\t\techo $SERVER is not running\n\tfi\n}\n\nfunction stop() \n{\n\tif [ \"`pgrep $SERVER -u $UID`\" != \"\" ];then\n\t\tkill -9 `pgrep $SERVER -u $UID`\n\tfi\n\n\techo \"sleeping...\" &&  sleep $INTERVAL\n\n\tif [ \"`pgrep $SERVER -u $UID`\" != \"\" ];then\n\t\techo \"$SERVER stop failed\"\n\t\texit 1\n\tfi\n}\n\ncase \"$1\" in\n\t'start')\n\tstart\n\t;;  \n\t'stop')\n\tstop\n\t;;  \n\t'status')\n\tstatus\n\t;;  \n\t'restart')\n\tstop && start\n\t;;  \n\t*)  \n\techo \"usage: $0 {start|stop|restart|status}\"\n\texit 1\n\t;;  \nesac\n"
  },
  {
    "path": "demo16/conf/config.yaml",
    "content": "runmode: debug                 # 开发模式, debug, release, test\naddr: :8080                  # HTTP绑定端口\nname: apiserver              # API Server的名字\nurl: http://127.0.0.1:8080   # pingServer函数请求的API服务器的ip:port\nmax_ping_count: 10           # pingServer函数try的次数\njwt_secret: Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5\ntls:\n  addr: :8081\n  cert: conf/server.crt\n  key: conf/server.key\nlog:\n  writers: stdout\n  logger_level: DEBUG\n  logger_file: log/apiserver.log\n  log_format_text: true\n  rollingPolicy: size\n  log_rotate_date: 1\n  log_rotate_size: 1\n  log_backup_count: 7\ndb:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\ndocker_db:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\n"
  },
  {
    "path": "demo16/conf/server.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIID2TCCAsGgAwIBAgIJAI/8axTNy37vMA0GCSqGSIb3DQEBBQUAMIGCMQswCQYD\nVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO\nUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAkxMjcuMC4wLjEx\nGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNvbTAeFw0xODA2MDQwNjMwMzFaFw0y\nODA2MDEwNjMwMzFaMIGCMQswCQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYD\nVQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklU\nMRIwEAYDVQQDDAkxMjcuMC4wLjExGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNv\nbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKHbnpmaxHokFC8rnJlG\n42eL4C3tog7rffv96DrwDajdFNk8DeaNUpZ+R4X4YPecumsCsbol2dDC1IHHGJUN\nq/A1vRjyDptMLmzp30p1aqVPcz+j8GMXtZUflLgup+HeKZRdrsk/0SaH1C3ywUBm\nyDMNHUxEvoFj0x9aJoPutnkEk9miZt/kF2vxlTbKFM14/TecVr3ljNwTebJAqEw9\niaZpNXn/IQV2I2PTGlxjyT6XNsCchjMTJ9y/XS9rBeX6BFHgQsV52CW1mWz8FieM\n/nyF9hE/TZKCpMtHWEYIdBbrxxY0BlhMxEtyIFzTha5zltFrQJGUYz7RIlyuabjk\nor8CAwEAAaNQME4wHQYDVR0OBBYEFIjLBQlToqUDoyOLeUd6HeaqiiLJMB8GA1Ud\nIwQYMBaAFIjLBQlToqUDoyOLeUd6HeaqiiLJMAwGA1UdEwQFMAMBAf8wDQYJKoZI\nhvcNAQEFBQADggEBAHAqpqGNUNgYCr3JvrVULDqFKmqLpvDKfLd6kmtnFzvuZLSz\nANE2RXXqQ5Ms3F0oHqZQq4aOvgvfas5dBLoToPL/ip+GibQ7O/1PsPD5bZaczUel\nAnmClX4ewcOEWmZx9mbYOz2C8RREvKU3MoBERrWA33LquYH0+8W8WHyMKNNF97oD\nBgWZ8R1t3N59ySZyHcye7ddlJUubRbdyCGrHbmziHV3Cj61NJiwS2JRvhYkc71M6\n8921j63qEM6i0NusFYG/w0GM3x/OeaeY/om6x+IOhJp3vIa1JPSekVyNSCwDEc3B\n+8WmVFaeMMXwErSkuu8nCbqRP8J1jiKgzpG/92g=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "demo16/conf/server.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCh256ZmsR6JBQv\nK5yZRuNni+At7aIO6337/eg68A2o3RTZPA3mjVKWfkeF+GD3nLprArG6JdnQwtSB\nxxiVDavwNb0Y8g6bTC5s6d9KdWqlT3M/o/BjF7WVH5S4Lqfh3imUXa7JP9Emh9Qt\n8sFAZsgzDR1MRL6BY9MfWiaD7rZ5BJPZombf5Bdr8ZU2yhTNeP03nFa95YzcE3my\nQKhMPYmmaTV5/yEFdiNj0xpcY8k+lzbAnIYzEyfcv10vawXl+gRR4ELFedgltZls\n/BYnjP58hfYRP02SgqTLR1hGCHQW68cWNAZYTMRLciBc04Wuc5bRa0CRlGM+0SJc\nrmm45KK/AgMBAAECggEASNMWvgf7pPT8u+iEchaKFLnDqQaFZu8f5TRtu67shnDK\ng59YpcYqRZoVtjp17pLu8Vzp+FY1dY9jq+yXq+DV3qNfLI0kc01IiiqEE+1WiYCA\n2z541y0Av1LRSDl9wcuCq8Wm8der1AlDN1VFDCPyqb2Z1AoOKQtwH2ghcjUCltn4\nNxuMYOVdql+Idl/7PCEha9pW3DQvPqJVXRPvBh6tB7FFuUOIq4+EFRUWWZlyCJfQ\nfYxB9jcFUWmAUieV3iBajg6H6QVkj/STLSgKuSktEBM2mqyL4slHXFWRt7+NQikg\ntOEWnqcTC4Ei2Dyam4SBXHJ1wT8AJV4kMTrN1ZBRmQKBgQDOMcIGHyuBI1aHcvPW\nTkein7bslFAKsA1LBqnw+alWzSHMKEmfWbBjeW0FQTIGEYMXwIq5JhnNcS5Kb5Qt\nBZgbBPDb7S80DtJnkno3iJ6hpijE3X/29lezigIuGibO5MD0Na1am2BP0iU77ifV\nxpYvj/j/pkdcTLM7hRDImSeNIwKBgQDI9EUFkGi6H9NZEles+MCHenVRYUBT2PXe\nhmtd9d5f4v3CmRsXvl4Dl/slIrJ9kuZktAck3Ocx6D0hnZcj1mwq077K7H3QqKCl\nzRwB11H5v/imsZ1ey0hLVPhCzr2miRPM/9oL5B2st9eotzhyRcrMn+oYZSSt79S8\nQ3YDiZ7TtQKBgENSr7z79GJnvVrgR4kTagRJDZrVGgVDUjPK6zXI7mdu9rgH93HW\nAOeZv+TVUpX0pc7diO3G6OnRKIIZSFIi33UC+fl0ydK/fCdhBhKXwuOYsvsEL0Hd\nUOlICEoxM7adrfqOhBlvXdTyEkItEkiUXHkPEwe1rNsQF/05By/YAbftAoGAYB2F\njd2+WZezTN0bFl58J9CIoH31eKVDJEYCwJRC4nX9jcARV0/0Q5/DvcVUvf8vN2ds\nK1OFOTetVZC8o6WBYxKYJRLsMosVG3h5NuA4E06grYoyjQ6J644emEWuLCNQVzLg\npeNb1iqweb/4vZ9oGms6WqS14IPfqpRRs+t1DikCgYEAyFtQKlLmNm/EirOMqjS2\n4/zMRj4B6n8BB8ZJsVC/MkuD+4B03rY44pP324cJnqVIcug9Zoijffnh1qbtZEc/\nMhNCa4a2MY0qcd4r/43cGbjqv94En7e5TgkH/79W+lbR3uBayt33RuuKStauZkf/\n2eBi4LlGO5stoeD63h9Ten0=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "demo16/config/config.go",
    "content": "package config\n\nimport (\n\t\"strings\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n)\n\ntype Config struct {\n\tName string\n}\n\nfunc Init(cfg string) error {\n\tc := Config{\n\t\tName: cfg,\n\t}\n\n\t// 初始化配置文件\n\tif err := c.initConfig(); err != nil {\n\t\treturn err\n\t}\n\n\t// 初始化日志包\n\tc.initLog()\n\n\t// 监控配置文件变化并热加载程序\n\tc.watchConfig()\n\n\treturn nil\n}\n\nfunc (c *Config) initConfig() error {\n\tif c.Name != \"\" {\n\t\tviper.SetConfigFile(c.Name) // 如果指定了配置文件，则解析指定的配置文件\n\t} else {\n\t\tviper.AddConfigPath(\"conf\") // 如果没有指定配置文件，则解析默认的配置文件\n\t\tviper.SetConfigName(\"config\")\n\t}\n\tviper.SetConfigType(\"yaml\")     // 设置配置文件格式为YAML\n\tviper.AutomaticEnv()            // 读取匹配的环境变量\n\tviper.SetEnvPrefix(\"APISERVER\") // 读取环境变量的前缀为APISERVER\n\treplacer := strings.NewReplacer(\".\", \"_\")\n\tviper.SetEnvKeyReplacer(replacer)\n\tif err := viper.ReadInConfig(); err != nil { // viper解析配置文件\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *Config) initLog() {\n\tpassLagerCfg := log.PassLagerCfg{\n\t\tWriters:        viper.GetString(\"log.writers\"),\n\t\tLoggerLevel:    viper.GetString(\"log.logger_level\"),\n\t\tLoggerFile:     viper.GetString(\"log.logger_file\"),\n\t\tLogFormatText:  viper.GetBool(\"log.log_format_text\"),\n\t\tRollingPolicy:  viper.GetString(\"log.rollingPolicy\"),\n\t\tLogRotateDate:  viper.GetInt(\"log.log_rotate_date\"),\n\t\tLogRotateSize:  viper.GetInt(\"log.log_rotate_size\"),\n\t\tLogBackupCount: viper.GetInt(\"log.log_backup_count\"),\n\t}\n\n\tlog.InitWithConfig(&passLagerCfg)\n}\n\n// 监控配置文件变化并热加载程序\nfunc (c *Config) watchConfig() {\n\tviper.WatchConfig()\n\tviper.OnConfigChange(func(e fsnotify.Event) {\n\t\tlog.Infof(\"Config file changed: %s\", e.Name)\n\t})\n}\n"
  },
  {
    "path": "demo16/db.sql",
    "content": "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n/*!40101 SET NAMES utf8 */;\n/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;\n/*!40103 SET TIME_ZONE='+00:00' */;\n/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;\n/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n\nCREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */;\n\nUSE `db_apiserver`;\n\n--\n-- Table structure for table `tb_users`\n--\n\nDROP TABLE IF EXISTS `tb_users`;\n/*!40101 SET @saved_cs_client     = @@character_set_client */;\n/*!40101 SET character_set_client = utf8 */;\nCREATE TABLE `tb_users` (\n  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n  `username` varchar(255) NOT NULL,\n  `password` varchar(255) NOT NULL,\n  `createdAt` timestamp NULL DEFAULT NULL,\n  `updatedAt` timestamp NULL DEFAULT NULL,\n  `deletedAt` timestamp NULL DEFAULT NULL,\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `username` (`username`),\n  KEY `idx_tb_users_deletedAt` (`deletedAt`)\n) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;\n/*!40101 SET character_set_client = @saved_cs_client */;\n\n--\n-- Dumping data for table `tb_users`\n--\n\nLOCK TABLES `tb_users` WRITE;\n/*!40000 ALTER TABLE `tb_users` DISABLE KEYS */;\nINSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL);\n/*!40000 ALTER TABLE `tb_users` ENABLE KEYS */;\nUNLOCK TABLES;\n/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;\n\n/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;\n/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;\n/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n\n-- Dump completed on 2018-05-28  0:25:41\n"
  },
  {
    "path": "demo16/handler/handler.go",
    "content": "package handler\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype Response struct {\n\tCode    int         `json:\"code\"`\n\tMessage string      `json:\"message\"`\n\tData    interface{} `json:\"data\"`\n}\n\nfunc SendResponse(c *gin.Context, err error, data interface{}) {\n\tcode, message := errno.DecodeErr(err)\n\n\t// always return http.StatusOK\n\tc.JSON(http.StatusOK, Response{\n\t\tCode:    code,\n\t\tMessage: message,\n\t\tData:    data,\n\t})\n}\n"
  },
  {
    "path": "demo16/handler/sd/check.go",
    "content": "package sd\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/shirou/gopsutil/cpu\"\n\t\"github.com/shirou/gopsutil/disk\"\n\t\"github.com/shirou/gopsutil/load\"\n\t\"github.com/shirou/gopsutil/mem\"\n)\n\nconst (\n\tB  = 1\n\tKB = 1024 * B\n\tMB = 1024 * KB\n\tGB = 1024 * MB\n)\n\n// HealthCheck shows `OK` as the ping-pong result.\nfunc HealthCheck(c *gin.Context) {\n\tmessage := \"OK\"\n\tc.String(http.StatusOK, \"\\n\"+message)\n}\n\n// DiskCheck checks the disk usage.\nfunc DiskCheck(c *gin.Context) {\n\tu, _ := disk.Usage(\"/\")\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusOK\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n\n// CPUCheck checks the cpu usage.\nfunc CPUCheck(c *gin.Context) {\n\tcores, _ := cpu.Counts(false)\n\n\ta, _ := load.Avg()\n\tl1 := a.Load1\n\tl5 := a.Load5\n\tl15 := a.Load15\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif l5 >= float64(cores-1) {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if l5 >= float64(cores-2) {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Load average: %.2f, %.2f, %.2f | Cores: %d\", text, l1, l5, l15, cores)\n\tc.String(status, \"\\n\"+message)\n}\n\n// RAMCheck checks the disk usage.\nfunc RAMCheck(c *gin.Context) {\n\tu, _ := mem.VirtualMemory()\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n"
  },
  {
    "path": "demo16/handler/user/create.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// Create creates a new user account.\nfunc Create(c *gin.Context) {\n\tlog.Info(\"User Create function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\tvar r CreateRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tu := model.UserModel{\n\t\tUsername: r.Username,\n\t\tPassword: r.Password,\n\t}\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\t// Insert the user to the database.\n\tif err := u.Create(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\trsp := CreateResponse{\n\t\tUsername: r.Username,\n\t}\n\n\t// Show the user information.\n\tSendResponse(c, nil, rsp)\n}\n"
  },
  {
    "path": "demo16/handler/user/delete.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Delete delete an user by the user identifier.\nfunc Delete(c *gin.Context) {\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\tif err := model.DeleteUser(uint64(userId)); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo16/handler/user/get.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Get gets an user by the user identifier.\nfunc Get(c *gin.Context) {\n\tusername := c.Param(\"username\")\n\t// Get the user by the `username` from the database.\n\tuser, err := model.GetUser(username)\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrUserNotFound, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, user)\n}\n"
  },
  {
    "path": "demo16/handler/user/list.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/service\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n)\n\n// List list the users in the database.\nfunc List(c *gin.Context) {\n\tlog.Info(\"List function called.\")\n\tvar r ListRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tinfos, count, err := service.ListUser(r.Username, r.Offset, r.Limit)\n\tif err != nil {\n\t\tSendResponse(c, err, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, ListResponse{\n\t\tTotalCount: count,\n\t\tUserList:   infos,\n\t})\n}\n"
  },
  {
    "path": "demo16/handler/user/login.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/auth\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/pkg/token\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Login generates the authentication token\n// if the password was matched with the specified account.\nfunc Login(c *gin.Context) {\n\t// Binding the data with the user struct.\n\tvar u model.UserModel\n\tif err := c.Bind(&u); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\t// Get the user information by the login username.\n\td, err := model.GetUser(u.Username)\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrUserNotFound, nil)\n\t\treturn\n\t}\n\n\t// Compare the login password with the user password.\n\tif err := auth.Compare(d.Password, u.Password); err != nil {\n\t\tSendResponse(c, errno.ErrPasswordIncorrect, nil)\n\t\treturn\n\t}\n\n\t// Sign the json web token.\n\tt, err := token.Sign(c, token.Context{ID: d.Id, Username: d.Username}, \"\")\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrToken, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, model.Token{Token: t})\n}\n"
  },
  {
    "path": "demo16/handler/user/update.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// Update update a exist user account info.\nfunc Update(c *gin.Context) {\n\tlog.Info(\"Update function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\t// Get the user id from the url parameter.\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\n\t// Binding the user data.\n\tvar u model.UserModel\n\tif err := c.Bind(&u); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\t// We update the record based on the user id.\n\tu.Id = uint64(userId)\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\n\t// Save changed fields.\n\tif err := u.Update(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo16/handler/user/user.go",
    "content": "package user\n\nimport (\n\t\"apiserver/model\"\n)\n\ntype CreateRequest struct {\n\tUsername string `json:\"username\"`\n\tPassword string `json:\"password\"`\n}\n\ntype CreateResponse struct {\n\tUsername string `json:\"username\"`\n}\n\ntype ListRequest struct {\n\tUsername string `json:\"username\"`\n\tOffset   int    `json:\"offset\"`\n\tLimit    int    `json:\"limit\"`\n}\n\ntype ListResponse struct {\n\tTotalCount uint64            `json:\"totalCount\"`\n\tUserList   []*model.UserInfo `json:\"userList\"`\n}\n"
  },
  {
    "path": "demo16/main.go",
    "content": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t_ \"net/http/pprof\"\n\t\"os\"\n\t\"time\"\n\n\t\"apiserver/config\"\n\t\"apiserver/model\"\n\tv \"apiserver/pkg/version\"\n\t\"apiserver/router\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tcfg     = pflag.StringP(\"config\", \"c\", \"\", \"apiserver config file path.\")\n\tversion = pflag.BoolP(\"version\", \"v\", false, \"show version info.\")\n)\n\nfunc main() {\n\tpflag.Parse()\n\tif *version {\n\t\tv := v.Get()\n\t\tmarshalled, err := json.MarshalIndent(&v, \"\", \"  \")\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"%v\\n\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tfmt.Println(string(marshalled))\n\t\treturn\n\t}\n\n\t// init config\n\tif err := config.Init(*cfg); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// init db\n\tmodel.DB.Init()\n\tdefer model.DB.Close()\n\n\t// Set gin mode.\n\tgin.SetMode(viper.GetString(\"runmode\"))\n\n\t// Create the Gin engine.\n\tg := gin.New()\n\n\t// Routes.\n\trouter.Load(\n\t\t// Cores.\n\t\tg,\n\n\t\t// Middlwares.\n\t\tmiddleware.Logging(),\n\t\tmiddleware.RequestId(),\n\t)\n\n\t// Ping the server to make sure the router is working.\n\tgo func() {\n\t\tif err := pingServer(); err != nil {\n\t\t\tlog.Fatal(\"The router has no response, or it might took too long to start up.\", err)\n\t\t}\n\t\tlog.Info(\"The router has been deployed successfully.\")\n\t}()\n\n\t// Start to listening the incoming requests.\n\tcert := viper.GetString(\"tls.cert\")\n\tkey := viper.GetString(\"tls.key\")\n\tif cert != \"\" && key != \"\" {\n\t\tgo func() {\n\t\t\tlog.Infof(\"Start to listening the incoming requests on https address: %s\", viper.GetString(\"tls.addr\"))\n\t\t\tlog.Info(http.ListenAndServeTLS(viper.GetString(\"tls.addr\"), cert, key, g).Error())\n\t\t}()\n\t}\n\n\tlog.Infof(\"Start to listening the incoming requests on http address: %s\", viper.GetString(\"addr\"))\n\tlog.Info(http.ListenAndServe(viper.GetString(\"addr\"), g).Error())\n}\n\n// pingServer pings the http server to make sure the router is working.\nfunc pingServer() error {\n\tfor i := 0; i < viper.GetInt(\"max_ping_count\"); i++ {\n\t\t// Ping the server by sending a GET request to `/health`.\n\t\tresp, err := http.Get(viper.GetString(\"url\") + \"/sd/health\")\n\t\tif err == nil && resp.StatusCode == 200 {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Sleep for a second to continue the next ping.\n\t\tlog.Info(\"Waiting for the router, retry in 1 second.\")\n\t\ttime.Sleep(time.Second)\n\t}\n\treturn errors.New(\"Cannot connect to the router.\")\n}\n"
  },
  {
    "path": "demo16/model/init.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n\t// MySQL driver.\n\t\"github.com/jinzhu/gorm\"\n\t_ \"github.com/jinzhu/gorm/dialects/mysql\"\n)\n\ntype Database struct {\n\tSelf   *gorm.DB\n\tDocker *gorm.DB\n}\n\nvar DB *Database\n\nfunc openDB(username, password, addr, name string) *gorm.DB {\n\tconfig := fmt.Sprintf(\"%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s\",\n\t\tusername,\n\t\tpassword,\n\t\taddr,\n\t\tname,\n\t\ttrue,\n\t\t//\"Asia/Shanghai\"),\n\t\t\"Local\")\n\n\tdb, err := gorm.Open(\"mysql\", config)\n\tif err != nil {\n\t\tlog.Errorf(err, \"Database connection failed. Database name: %s\", name)\n\t}\n\n\t// set for db connection\n\tsetupDB(db)\n\n\treturn db\n}\n\nfunc setupDB(db *gorm.DB) {\n\tdb.LogMode(viper.GetBool(\"gormlog\"))\n\t//db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数，默认值为0表示不限制.设置最大的连接数，可以避免并发太高导致连接mysql出现too many connections的错误。\n\tdb.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。\n}\n\n// used for cli\nfunc InitSelfDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"db.username\"),\n\t\tviper.GetString(\"db.password\"),\n\t\tviper.GetString(\"db.addr\"),\n\t\tviper.GetString(\"db.name\"))\n}\n\nfunc GetSelfDB() *gorm.DB {\n\treturn InitSelfDB()\n}\n\nfunc InitDockerDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"docker_db.username\"),\n\t\tviper.GetString(\"docker_db.password\"),\n\t\tviper.GetString(\"docker_db.addr\"),\n\t\tviper.GetString(\"docker_db.name\"))\n}\n\nfunc GetDockerDB() *gorm.DB {\n\treturn InitDockerDB()\n}\n\nfunc (db *Database) Init() {\n\tDB = &Database{\n\t\tSelf:   GetSelfDB(),\n\t\tDocker: GetDockerDB(),\n\t}\n}\n\nfunc (db *Database) Close() {\n\tDB.Self.Close()\n\tDB.Docker.Close()\n}\n"
  },
  {
    "path": "demo16/model/model.go",
    "content": "package model\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype BaseModel struct {\n\tId        uint64     `gorm:\"primary_key;AUTO_INCREMENT;column:id\" json:\"-\"`\n\tCreatedAt time.Time  `gorm:\"column:createdAt\" json:\"-\"`\n\tUpdatedAt time.Time  `gorm:\"column:updatedAt\" json:\"-\"`\n\tDeletedAt *time.Time `gorm:\"column:deletedAt\" sql:\"index\" json:\"-\"`\n}\n\ntype UserInfo struct {\n\tId        uint64 `json:\"id\"`\n\tUsername  string `json:\"username\"`\n\tSayHello  string `json:\"sayHello\"`\n\tPassword  string `json:\"password\"`\n\tCreatedAt string `json:\"createdAt\"`\n\tUpdatedAt string `json:\"updatedAt\"`\n}\n\ntype UserList struct {\n\tLock  *sync.Mutex\n\tIdMap map[uint64]*UserInfo\n}\n\n// Token represents a JSON web token.\ntype Token struct {\n\tToken string `json:\"token\"`\n}\n"
  },
  {
    "path": "demo16/model/user.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"apiserver/pkg/auth\"\n\t\"apiserver/pkg/constvar\"\n\n\tvalidator \"gopkg.in/go-playground/validator.v9\"\n)\n\n// User represents a registered user.\ntype UserModel struct {\n\tBaseModel\n\tUsername string `json:\"username\" gorm:\"column:username;not null\" binding:\"required\" validate:\"min=1,max=32\"`\n\tPassword string `json:\"password\" gorm:\"column:password;not null\" binding:\"required\" validate:\"min=5,max=128\"`\n}\n\nfunc (c *UserModel) TableName() string {\n\treturn \"tb_users\"\n}\n\n// Create creates a new user account.\nfunc (u *UserModel) Create() error {\n\treturn DB.Self.Create(&u).Error\n}\n\n// DeleteUser deletes the user by the user identifier.\nfunc DeleteUser(id uint64) error {\n\tuser := UserModel{}\n\tuser.BaseModel.Id = id\n\treturn DB.Self.Delete(&user).Error\n}\n\n// Update updates an user account information.\nfunc (u *UserModel) Update() error {\n\treturn DB.Self.Save(u).Error\n}\n\n// GetUser gets an user by the user identifier.\nfunc GetUser(username string) (*UserModel, error) {\n\tu := &UserModel{}\n\td := DB.Self.Where(\"username = ?\", username).First(&u)\n\treturn u, d.Error\n}\n\n// ListUser List all users\nfunc ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) {\n\tif limit == 0 {\n\t\tlimit = constvar.DefaultLimit\n\t}\n\n\tusers := make([]*UserModel, 0)\n\tvar count uint64\n\n\twhere := fmt.Sprintf(\"username like '%%%s%%'\", username)\n\tif err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\tif err := DB.Self.Where(where).Offset(offset).Limit(limit).Order(\"id desc\").Find(&users).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\treturn users, count, nil\n}\n\n// Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct).\nfunc (u *UserModel) Compare(pwd string) (err error) {\n\terr = auth.Compare(u.Password, pwd)\n\treturn\n}\n\n// Encrypt the user password.\nfunc (u *UserModel) Encrypt() (err error) {\n\tu.Password, err = auth.Encrypt(u.Password)\n\treturn\n}\n\n// Validate the fields.\nfunc (u *UserModel) Validate() error {\n\tvalidate := validator.New()\n\treturn validate.Struct(u)\n}\n"
  },
  {
    "path": "demo16/pkg/auth/auth.go",
    "content": "package auth\n\nimport \"golang.org/x/crypto/bcrypt\"\n\n// Encrypt encrypts the plain text with bcrypt.\nfunc Encrypt(source string) (string, error) {\n\thashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost)\n\treturn string(hashedBytes), err\n}\n\n// Compare compares the encrypted text with the plain text if it's the same.\nfunc Compare(hashedPassword, password string) error {\n\treturn bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))\n}\n"
  },
  {
    "path": "demo16/pkg/constvar/constvar.go",
    "content": "package constvar\n\nconst (\n\tDefaultLimit = 50\n)\n"
  },
  {
    "path": "demo16/pkg/errno/code.go",
    "content": "package errno\n\nvar (\n\t// Common errors\n\tOK                  = &Errno{Code: 0, Message: \"OK\"}\n\tInternalServerError = &Errno{Code: 10001, Message: \"Internal server error\"}\n\tErrBind             = &Errno{Code: 10002, Message: \"Error occurred while binding the request body to the struct.\"}\n\n\tErrValidation = &Errno{Code: 20001, Message: \"Validation failed.\"}\n\tErrDatabase   = &Errno{Code: 20002, Message: \"Database error.\"}\n\tErrToken      = &Errno{Code: 20003, Message: \"Error occurred while signing the JSON web token.\"}\n\n\t// user errors\n\tErrEncrypt           = &Errno{Code: 20101, Message: \"Error occurred while encrypting the user password.\"}\n\tErrUserNotFound      = &Errno{Code: 20102, Message: \"The user was not found.\"}\n\tErrTokenInvalid      = &Errno{Code: 20103, Message: \"The token was invalid.\"}\n\tErrPasswordIncorrect = &Errno{Code: 20104, Message: \"The password was incorrect.\"}\n)\n"
  },
  {
    "path": "demo16/pkg/errno/errno.go",
    "content": "package errno\n\nimport \"fmt\"\n\ntype Errno struct {\n\tCode    int\n\tMessage string\n}\n\nfunc (err Errno) Error() string {\n\treturn err.Message\n}\n\n// Err represents an error\ntype Err struct {\n\tCode    int\n\tMessage string\n\tErr     error\n}\n\nfunc New(errno *Errno, err error) *Err {\n\treturn &Err{Code: errno.Code, Message: errno.Message, Err: err}\n}\n\nfunc (err *Err) Add(message string) error {\n\terr.Message += \" \" + message\n\treturn err\n}\n\nfunc (err *Err) Addf(format string, args ...interface{}) error {\n\terr.Message += \" \" + fmt.Sprintf(format, args...)\n\treturn err\n}\n\nfunc (err *Err) Error() string {\n\treturn fmt.Sprintf(\"Err - code: %d, message: %s, error: %s\", err.Code, err.Message, err.Err)\n}\n\nfunc IsErrUserNotFound(err error) bool {\n\tcode, _ := DecodeErr(err)\n\treturn code == ErrUserNotFound.Code\n}\n\nfunc DecodeErr(err error) (int, string) {\n\tif err == nil {\n\t\treturn OK.Code, OK.Message\n\t}\n\n\tswitch typed := err.(type) {\n\tcase *Err:\n\t\treturn typed.Code, typed.Message\n\tcase *Errno:\n\t\treturn typed.Code, typed.Message\n\tdefault:\n\t}\n\n\treturn InternalServerError.Code, err.Error()\n}\n"
  },
  {
    "path": "demo16/pkg/token/token.go",
    "content": "package token\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\tjwt \"github.com/dgrijalva/jwt-go\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\t// ErrMissingHeader means the `Authorization` header was empty.\n\tErrMissingHeader = errors.New(\"The length of the `Authorization` header is zero.\")\n)\n\n// Context is the context of the JSON web token.\ntype Context struct {\n\tID       uint64\n\tUsername string\n}\n\n// secretFunc validates the secret format.\nfunc secretFunc(secret string) jwt.Keyfunc {\n\treturn func(token *jwt.Token) (interface{}, error) {\n\t\t// Make sure the `alg` is what we except.\n\t\tif _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {\n\t\t\treturn nil, jwt.ErrSignatureInvalid\n\t\t}\n\n\t\treturn []byte(secret), nil\n\t}\n}\n\n// Parse validates the token with the specified secret,\n// and returns the context if the token was valid.\nfunc Parse(tokenString string, secret string) (*Context, error) {\n\tctx := &Context{}\n\n\t// Parse the token.\n\ttoken, err := jwt.Parse(tokenString, secretFunc(secret))\n\n\t// Parse error.\n\tif err != nil {\n\t\treturn ctx, err\n\n\t\t// Read the token if it's valid.\n\t} else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {\n\t\tctx.ID = uint64(claims[\"id\"].(float64))\n\t\tctx.Username = claims[\"username\"].(string)\n\t\treturn ctx, nil\n\n\t\t// Other errors.\n\t} else {\n\t\treturn ctx, err\n\t}\n}\n\n// ParseRequest gets the token from the header and\n// pass it to the Parse function to parses the token.\nfunc ParseRequest(c *gin.Context) (*Context, error) {\n\theader := c.Request.Header.Get(\"Authorization\")\n\n\t// Load the jwt secret from config\n\tsecret := viper.GetString(\"jwt_secret\")\n\n\tif len(header) == 0 {\n\t\treturn &Context{}, ErrMissingHeader\n\t}\n\n\tvar t string\n\t// Parse the header to get the token part.\n\tfmt.Sscanf(header, \"Bearer %s\", &t)\n\treturn Parse(t, secret)\n}\n\n// Sign signs the context with the specified secret.\nfunc Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) {\n\t// Load the jwt secret from the Gin config if the secret isn't specified.\n\tif secret == \"\" {\n\t\tsecret = viper.GetString(\"jwt_secret\")\n\t}\n\t// The token content.\n\ttoken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{\n\t\t\"id\":       c.ID,\n\t\t\"username\": c.Username,\n\t\t\"nbf\":      time.Now().Unix(),\n\t\t\"iat\":      time.Now().Unix(),\n\t})\n\t// Sign the token with the specified secret.\n\ttokenString, err = token.SignedString([]byte(secret))\n\n\treturn\n}\n"
  },
  {
    "path": "demo16/pkg/version/base.go",
    "content": "package version\n\nvar (\n\tgitTag       string = \"\"\n\tgitCommit    string = \"$Format:%H$\"          // sha1 from git, output of $(git rev-parse HEAD)\n\tgitTreeState string = \"not a git tree\"       // state of git tree, either \"clean\" or \"dirty\"\n\tbuildDate    string = \"1970-01-01T00:00:00Z\" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')\n)\n"
  },
  {
    "path": "demo16/pkg/version/doc.go",
    "content": "package version\n"
  },
  {
    "path": "demo16/pkg/version/version.go",
    "content": "package version\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n)\n\n// Info contains versioning information.\ntype Info struct {\n\tGitTag       string `json:\"gitTag\"`\n\tGitCommit    string `json:\"gitCommit\"`\n\tGitTreeState string `json:\"gitTreeState\"`\n\tBuildDate    string `json:\"buildDate\"`\n\tGoVersion    string `json:\"goVersion\"`\n\tCompiler     string `json:\"compiler\"`\n\tPlatform     string `json:\"platform\"`\n}\n\n// String returns info as a human-friendly version string.\nfunc (info Info) String() string {\n\treturn info.GitTag\n}\n\nfunc Get() Info {\n\treturn Info{\n\t\tGitTag:       gitTag,\n\t\tGitCommit:    gitCommit,\n\t\tGitTreeState: gitTreeState,\n\t\tBuildDate:    buildDate,\n\t\tGoVersion:    runtime.Version(),\n\t\tCompiler:     runtime.Compiler,\n\t\tPlatform:     fmt.Sprintf(\"%s/%s\", runtime.GOOS, runtime.GOARCH),\n\t}\n}\n"
  },
  {
    "path": "demo16/router/middleware/auth.go",
    "content": "package middleware\n\nimport (\n\t\"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/pkg/token\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc AuthMiddleware() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// Parse the json web token.\n\t\tif _, err := token.ParseRequest(c); err != nil {\n\t\t\thandler.SendResponse(c, errno.ErrTokenInvalid, nil)\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "demo16/router/middleware/header.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// NoCache is a middleware function that appends headers\n// to prevent the client from caching the HTTP response.\nfunc NoCache(c *gin.Context) {\n\tc.Header(\"Cache-Control\", \"no-cache, no-store, max-age=0, must-revalidate, value\")\n\tc.Header(\"Expires\", \"Thu, 01 Jan 1970 00:00:00 GMT\")\n\tc.Header(\"Last-Modified\", time.Now().UTC().Format(http.TimeFormat))\n\tc.Next()\n}\n\n// Options is a middleware function that appends headers\n// for options requests and aborts then exits the middleware\n// chain and ends the request.\nfunc Options(c *gin.Context) {\n\tif c.Request.Method != \"OPTIONS\" {\n\t\tc.Next()\n\t} else {\n\t\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\t\tc.Header(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Access-Control-Allow-Headers\", \"authorization, origin, content-type, accept\")\n\t\tc.Header(\"Allow\", \"HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Content-Type\", \"application/json\")\n\t\tc.AbortWithStatus(200)\n\t}\n}\n\n// Secure is a middleware function that appends security\n// and resource access headers.\nfunc Secure(c *gin.Context) {\n\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\tc.Header(\"X-Frame-Options\", \"DENY\")\n\tc.Header(\"X-Content-Type-Options\", \"nosniff\")\n\tc.Header(\"X-XSS-Protection\", \"1; mode=block\")\n\tif c.Request.TLS != nil {\n\t\tc.Header(\"Strict-Transport-Security\", \"max-age=31536000\")\n\t}\n\n\t// Also consider adding Content-Security-Policy headers\n\t// c.Header(\"Content-Security-Policy\", \"script-src 'self' https://cdnjs.cloudflare.com\")\n}\n"
  },
  {
    "path": "demo16/router/middleware/logging.go",
    "content": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/willf/pad\"\n)\n\ntype bodyLogWriter struct {\n\tgin.ResponseWriter\n\tbody *bytes.Buffer\n}\n\nfunc (w bodyLogWriter) Write(b []byte) (int, error) {\n\tw.body.Write(b)\n\treturn w.ResponseWriter.Write(b)\n}\n\n// Logging is a middleware function that logs the each request.\nfunc Logging() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tstart := time.Now().UTC()\n\t\tpath := c.Request.URL.Path\n\n\t\treg := regexp.MustCompile(\"(/v1/user|/login)\")\n\t\tif !reg.MatchString(path) {\n\t\t\treturn\n\t\t}\n\n\t\t// Skip for the health check requests.\n\t\tif path == \"/sd/health\" || path == \"/sd/ram\" || path == \"/sd/cpu\" || path == \"/sd/disk\" {\n\t\t\treturn\n\t\t}\n\n\t\t// Read the Body content\n\t\tvar bodyBytes []byte\n\t\tif c.Request.Body != nil {\n\t\t\tbodyBytes, _ = ioutil.ReadAll(c.Request.Body)\n\t\t}\n\n\t\t// Restore the io.ReadCloser to its original state\n\t\tc.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))\n\n\t\t// The basic informations.\n\t\tmethod := c.Request.Method\n\t\tip := c.ClientIP()\n\n\t\t//log.Debugf(\"New request come in, path: %s, Method: %s, body `%s`\", path, method, string(bodyBytes))\n\t\tblw := &bodyLogWriter{\n\t\t\tbody:           bytes.NewBufferString(\"\"),\n\t\t\tResponseWriter: c.Writer,\n\t\t}\n\t\tc.Writer = blw\n\n\t\t// Continue.\n\t\tc.Next()\n\n\t\t// Calculates the latency.\n\t\tend := time.Now().UTC()\n\t\tlatency := end.Sub(start)\n\n\t\tcode, message := -1, \"\"\n\n\t\t// get code and message\n\t\tvar response handler.Response\n\t\tif err := json.Unmarshal(blw.body.Bytes(), &response); err != nil {\n\t\t\tlog.Errorf(err, \"response body can not unmarshal to model.Response struct, body: `%s`\", blw.body.Bytes())\n\t\t\tcode = errno.InternalServerError.Code\n\t\t\tmessage = err.Error()\n\t\t} else {\n\t\t\tcode = response.Code\n\t\t\tmessage = response.Message\n\t\t}\n\n\t\tlog.Infof(\"%-13s | %-12s | %s %s | {code: %d, message: %s}\", latency, ip, pad.Right(method, 5, \"\"), path, code, message)\n\t}\n}\n"
  },
  {
    "path": "demo16/router/middleware/requestid.go",
    "content": "package middleware\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/satori/go.uuid\"\n)\n\nfunc RequestId() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// Check for incoming header, use it if exists\n\t\trequestId := c.Request.Header.Get(\"X-Request-Id\")\n\n\t\t// Create request id with UUID4\n\t\tif requestId == \"\" {\n\t\t\tu4, _ := uuid.NewV4()\n\t\t\trequestId = u4.String()\n\t\t}\n\n\t\t// Expose it for use in the application\n\t\tc.Set(\"X-Request-Id\", requestId)\n\n\t\t// Set X-Request-Id header\n\t\tc.Writer.Header().Set(\"X-Request-Id\", requestId)\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "demo16/router/router.go",
    "content": "package router\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/handler/sd\"\n\t\"apiserver/handler/user\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-contrib/pprof\"\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Load loads the middlewares, routes, handlers.\nfunc Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {\n\t// Middlewares.\n\tg.Use(gin.Recovery())\n\tg.Use(middleware.NoCache)\n\tg.Use(middleware.Options)\n\tg.Use(middleware.Secure)\n\tg.Use(mw...)\n\t// 404 Handler.\n\tg.NoRoute(func(c *gin.Context) {\n\t\tc.String(http.StatusNotFound, \"The incorrect API route.\")\n\t})\n\n\t// pprof router\n\tpprof.Register(g)\n\n\t// api for authentication functionalities\n\tg.POST(\"/login\", user.Login)\n\n\t// The user handlers, requiring authentication\n\tu := g.Group(\"/v1/user\")\n\tu.Use(middleware.AuthMiddleware())\n\t{\n\t\tu.POST(\"\", user.Create)\n\t\tu.DELETE(\"/:id\", user.Delete)\n\t\tu.PUT(\"/:id\", user.Update)\n\t\tu.GET(\"\", user.List)\n\t\tu.GET(\"/:username\", user.Get)\n\t}\n\n\t// The health check handlers\n\tsvcd := g.Group(\"/sd\")\n\t{\n\t\tsvcd.GET(\"/health\", sd.HealthCheck)\n\t\tsvcd.GET(\"/disk\", sd.DiskCheck)\n\t\tsvcd.GET(\"/cpu\", sd.CPUCheck)\n\t\tsvcd.GET(\"/ram\", sd.RAMCheck)\n\t}\n\n\treturn g\n}\n"
  },
  {
    "path": "demo16/service/service.go",
    "content": "package service\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\n\t\"apiserver/model\"\n\t\"apiserver/util\"\n)\n\nfunc ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) {\n\tinfos := make([]*model.UserInfo, 0)\n\tusers, count, err := model.ListUser(username, offset, limit)\n\tif err != nil {\n\t\treturn nil, count, err\n\t}\n\n\tids := []uint64{}\n\tfor _, user := range users {\n\t\tids = append(ids, user.Id)\n\t}\n\n\twg := sync.WaitGroup{}\n\tuserList := model.UserList{\n\t\tLock:  new(sync.Mutex),\n\t\tIdMap: make(map[uint64]*model.UserInfo, len(users)),\n\t}\n\n\terrChan := make(chan error, 1)\n\tfinished := make(chan bool, 1)\n\n\t// Improve query efficiency in parallel\n\tfor _, u := range users {\n\t\twg.Add(1)\n\t\tgo func(u *model.UserModel) {\n\t\t\tdefer wg.Done()\n\n\t\t\tshortId, err := util.GenShortId()\n\t\t\tif err != nil {\n\t\t\t\terrChan <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tuserList.Lock.Lock()\n\t\t\tdefer userList.Lock.Unlock()\n\t\t\tuserList.IdMap[u.Id] = &model.UserInfo{\n\t\t\t\tId:        u.Id,\n\t\t\t\tUsername:  u.Username,\n\t\t\t\tSayHello:  fmt.Sprintf(\"Hello %s\", shortId),\n\t\t\t\tPassword:  u.Password,\n\t\t\t\tCreatedAt: u.CreatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t\tUpdatedAt: u.UpdatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t}\n\t\t}(u)\n\t}\n\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(finished)\n\t}()\n\n\tselect {\n\tcase <-finished:\n\tcase err := <-errChan:\n\t\treturn nil, count, err\n\t}\n\n\tfor _, id := range ids {\n\t\tinfos = append(infos, userList.IdMap[id])\n\t}\n\n\treturn infos, count, nil\n}\n"
  },
  {
    "path": "demo16/util/util.go",
    "content": "package util\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/teris-io/shortid\"\n)\n\nfunc GenShortId() (string, error) {\n\treturn shortid.Generate()\n}\n\nfunc GetReqID(c *gin.Context) string {\n\tv, ok := c.Get(\"X-Request-Id\")\n\tif !ok {\n\t\treturn \"\"\n\t}\n\tif requestId, ok := v.(string); ok {\n\t\treturn requestId\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "demo16/util/util_test.go",
    "content": "package util\n\nimport (\n\t\"testing\"\n)\n\nfunc TestGenShortId(t *testing.T) {\n\tshortId, err := GenShortId()\n\tif shortId == \"\" || err != nil {\n\t\tt.Error(\"GenShortId failed!\")\n\t}\n\n\tt.Log(\"GenShortId test pass\")\n}\n\nfunc BenchmarkGenShortId(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\tGenShortId()\n\t}\n}\n\nfunc BenchmarkGenShortIdTimeConsuming(b *testing.B) {\n\tb.StopTimer() //调用该函数停止压力测试的时间计数\n\n\tshortId, err := GenShortId()\n\tif shortId == \"\" || err != nil {\n\t\tb.Error(err)\n\t}\n\n\tb.StartTimer() //重新开始时间\n\n\tfor i := 0; i < b.N; i++ {\n\t\tGenShortId()\n\t}\n}\n"
  },
  {
    "path": "demo17/Makefile",
    "content": "SHELL := /bin/bash\nBASEDIR = $(shell pwd)\n\n# build with verison infos\nversionDir = \"apiserver/pkg/version\"\ngitTag = $(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)\nbuildDate = $(shell TZ=Asia/Shanghai date +%FT%T%z)\ngitCommit = $(shell git log --pretty=format:'%H' -n 1)\ngitTreeState = $(shell if git status|grep -q 'clean';then echo clean; else echo dirty; fi)\n\nldflags=\"-w -X ${versionDir}.gitTag=${gitTag} -X ${versionDir}.buildDate=${buildDate} -X ${versionDir}.gitCommit=${gitCommit} -X ${versionDir}.gitTreeState=${gitTreeState}\"\n\nall: gotool\n\t@go build -v -ldflags ${ldflags} .\nclean:\n\trm -f apiserver\n\tfind . -name \"[._]*.s[a-w][a-z]\" | xargs -i rm -f {}\ngotool:\n\tgofmt -w .\n\tgo tool vet . |& grep -v vendor;true\nca:\n\topenssl 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\"\n\nhelp:\n\t@echo \"make - compile the source code\"\n\t@echo \"make clean - remove binary file and vim swp files\"\n\t@echo \"make gotool - run go tool 'fmt' and 'vet'\"\n\t@echo \"make ca - generate ca files\"\n\n.PHONY: clean gotool ca help\n\n\n"
  },
  {
    "path": "demo17/README.md",
    "content": "进阶：生成Swagger在线文档\n"
  },
  {
    "path": "demo17/admin.sh",
    "content": "#!/bin/bash\n\nSERVER=\"apiserver\"\nBASE_DIR=$PWD\nINTERVAL=2\n\n# 命令行参数，需要手动指定\nARGS=\"\"\n\nfunction start()\n{\n\tif [ \"`pgrep $SERVER -u $UID`\" != \"\" ];then\n\t\techo \"$SERVER already running\"\n\t\texit 1\n\tfi\n\n\tnohup $BASE_DIR/$SERVER $ARGS  server &>/dev/null &\n\n\techo \"sleeping...\" &&  sleep $INTERVAL\n\n\t# check status\n\tif [ \"`pgrep $SERVER -u $UID`\" == \"\" ];then\n\t\techo \"$SERVER start failed\"\n\t\texit 1\n\tfi\n}\n\nfunction status() \n{\n\tif [ \"`pgrep $SERVER -u $UID`\" != \"\" ];then\n\t\techo $SERVER is running\n\telse\n\t\techo $SERVER is not running\n\tfi\n}\n\nfunction stop() \n{\n\tif [ \"`pgrep $SERVER -u $UID`\" != \"\" ];then\n\t\tkill -9 `pgrep $SERVER -u $UID`\n\tfi\n\n\techo \"sleeping...\" &&  sleep $INTERVAL\n\n\tif [ \"`pgrep $SERVER -u $UID`\" != \"\" ];then\n\t\techo \"$SERVER stop failed\"\n\t\texit 1\n\tfi\n}\n\ncase \"$1\" in\n\t'start')\n\tstart\n\t;;  \n\t'stop')\n\tstop\n\t;;  \n\t'status')\n\tstatus\n\t;;  \n\t'restart')\n\tstop && start\n\t;;  \n\t*)  \n\techo \"usage: $0 {start|stop|restart|status}\"\n\texit 1\n\t;;  \nesac\n"
  },
  {
    "path": "demo17/conf/config.yaml",
    "content": "runmode: debug                 # 开发模式, debug, release, test\naddr: :8080                  # HTTP绑定端口\nname: apiserver              # API Server的名字\nurl: http://127.0.0.1:8080   # pingServer函数请求的API服务器的ip:port\nmax_ping_count: 10           # pingServer函数try的次数\njwt_secret: Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5\ntls:\n  addr: :8081\n  cert: conf/server.crt\n  key: conf/server.key\nlog:\n  writers: stdout\n  logger_level: DEBUG\n  logger_file: log/apiserver.log\n  log_format_text: true\n  rollingPolicy: size\n  log_rotate_date: 1\n  log_rotate_size: 1\n  log_backup_count: 7\ndb:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\ndocker_db:\n  name: db_apiserver\n  addr: 127.0.0.1:3306\n  username: root\n  password: root\n"
  },
  {
    "path": "demo17/conf/server.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIID2TCCAsGgAwIBAgIJAI/8axTNy37vMA0GCSqGSIb3DQEBBQUAMIGCMQswCQYD\nVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO\nUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAkxMjcuMC4wLjEx\nGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNvbTAeFw0xODA2MDQwNjMwMzFaFw0y\nODA2MDEwNjMwMzFaMIGCMQswCQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYD\nVQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklU\nMRIwEAYDVQQDDAkxMjcuMC4wLjExGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNv\nbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKHbnpmaxHokFC8rnJlG\n42eL4C3tog7rffv96DrwDajdFNk8DeaNUpZ+R4X4YPecumsCsbol2dDC1IHHGJUN\nq/A1vRjyDptMLmzp30p1aqVPcz+j8GMXtZUflLgup+HeKZRdrsk/0SaH1C3ywUBm\nyDMNHUxEvoFj0x9aJoPutnkEk9miZt/kF2vxlTbKFM14/TecVr3ljNwTebJAqEw9\niaZpNXn/IQV2I2PTGlxjyT6XNsCchjMTJ9y/XS9rBeX6BFHgQsV52CW1mWz8FieM\n/nyF9hE/TZKCpMtHWEYIdBbrxxY0BlhMxEtyIFzTha5zltFrQJGUYz7RIlyuabjk\nor8CAwEAAaNQME4wHQYDVR0OBBYEFIjLBQlToqUDoyOLeUd6HeaqiiLJMB8GA1Ud\nIwQYMBaAFIjLBQlToqUDoyOLeUd6HeaqiiLJMAwGA1UdEwQFMAMBAf8wDQYJKoZI\nhvcNAQEFBQADggEBAHAqpqGNUNgYCr3JvrVULDqFKmqLpvDKfLd6kmtnFzvuZLSz\nANE2RXXqQ5Ms3F0oHqZQq4aOvgvfas5dBLoToPL/ip+GibQ7O/1PsPD5bZaczUel\nAnmClX4ewcOEWmZx9mbYOz2C8RREvKU3MoBERrWA33LquYH0+8W8WHyMKNNF97oD\nBgWZ8R1t3N59ySZyHcye7ddlJUubRbdyCGrHbmziHV3Cj61NJiwS2JRvhYkc71M6\n8921j63qEM6i0NusFYG/w0GM3x/OeaeY/om6x+IOhJp3vIa1JPSekVyNSCwDEc3B\n+8WmVFaeMMXwErSkuu8nCbqRP8J1jiKgzpG/92g=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "demo17/conf/server.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCh256ZmsR6JBQv\nK5yZRuNni+At7aIO6337/eg68A2o3RTZPA3mjVKWfkeF+GD3nLprArG6JdnQwtSB\nxxiVDavwNb0Y8g6bTC5s6d9KdWqlT3M/o/BjF7WVH5S4Lqfh3imUXa7JP9Emh9Qt\n8sFAZsgzDR1MRL6BY9MfWiaD7rZ5BJPZombf5Bdr8ZU2yhTNeP03nFa95YzcE3my\nQKhMPYmmaTV5/yEFdiNj0xpcY8k+lzbAnIYzEyfcv10vawXl+gRR4ELFedgltZls\n/BYnjP58hfYRP02SgqTLR1hGCHQW68cWNAZYTMRLciBc04Wuc5bRa0CRlGM+0SJc\nrmm45KK/AgMBAAECggEASNMWvgf7pPT8u+iEchaKFLnDqQaFZu8f5TRtu67shnDK\ng59YpcYqRZoVtjp17pLu8Vzp+FY1dY9jq+yXq+DV3qNfLI0kc01IiiqEE+1WiYCA\n2z541y0Av1LRSDl9wcuCq8Wm8der1AlDN1VFDCPyqb2Z1AoOKQtwH2ghcjUCltn4\nNxuMYOVdql+Idl/7PCEha9pW3DQvPqJVXRPvBh6tB7FFuUOIq4+EFRUWWZlyCJfQ\nfYxB9jcFUWmAUieV3iBajg6H6QVkj/STLSgKuSktEBM2mqyL4slHXFWRt7+NQikg\ntOEWnqcTC4Ei2Dyam4SBXHJ1wT8AJV4kMTrN1ZBRmQKBgQDOMcIGHyuBI1aHcvPW\nTkein7bslFAKsA1LBqnw+alWzSHMKEmfWbBjeW0FQTIGEYMXwIq5JhnNcS5Kb5Qt\nBZgbBPDb7S80DtJnkno3iJ6hpijE3X/29lezigIuGibO5MD0Na1am2BP0iU77ifV\nxpYvj/j/pkdcTLM7hRDImSeNIwKBgQDI9EUFkGi6H9NZEles+MCHenVRYUBT2PXe\nhmtd9d5f4v3CmRsXvl4Dl/slIrJ9kuZktAck3Ocx6D0hnZcj1mwq077K7H3QqKCl\nzRwB11H5v/imsZ1ey0hLVPhCzr2miRPM/9oL5B2st9eotzhyRcrMn+oYZSSt79S8\nQ3YDiZ7TtQKBgENSr7z79GJnvVrgR4kTagRJDZrVGgVDUjPK6zXI7mdu9rgH93HW\nAOeZv+TVUpX0pc7diO3G6OnRKIIZSFIi33UC+fl0ydK/fCdhBhKXwuOYsvsEL0Hd\nUOlICEoxM7adrfqOhBlvXdTyEkItEkiUXHkPEwe1rNsQF/05By/YAbftAoGAYB2F\njd2+WZezTN0bFl58J9CIoH31eKVDJEYCwJRC4nX9jcARV0/0Q5/DvcVUvf8vN2ds\nK1OFOTetVZC8o6WBYxKYJRLsMosVG3h5NuA4E06grYoyjQ6J644emEWuLCNQVzLg\npeNb1iqweb/4vZ9oGms6WqS14IPfqpRRs+t1DikCgYEAyFtQKlLmNm/EirOMqjS2\n4/zMRj4B6n8BB8ZJsVC/MkuD+4B03rY44pP324cJnqVIcug9Zoijffnh1qbtZEc/\nMhNCa4a2MY0qcd4r/43cGbjqv94En7e5TgkH/79W+lbR3uBayt33RuuKStauZkf/\n2eBi4LlGO5stoeD63h9Ten0=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "demo17/config/config.go",
    "content": "package config\n\nimport (\n\t\"strings\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n)\n\ntype Config struct {\n\tName string\n}\n\nfunc Init(cfg string) error {\n\tc := Config{\n\t\tName: cfg,\n\t}\n\n\t// 初始化配置文件\n\tif err := c.initConfig(); err != nil {\n\t\treturn err\n\t}\n\n\t// 初始化日志包\n\tc.initLog()\n\n\t// 监控配置文件变化并热加载程序\n\tc.watchConfig()\n\n\treturn nil\n}\n\nfunc (c *Config) initConfig() error {\n\tif c.Name != \"\" {\n\t\tviper.SetConfigFile(c.Name) // 如果指定了配置文件，则解析指定的配置文件\n\t} else {\n\t\tviper.AddConfigPath(\"conf\") // 如果没有指定配置文件，则解析默认的配置文件\n\t\tviper.SetConfigName(\"config\")\n\t}\n\tviper.SetConfigType(\"yaml\")     // 设置配置文件格式为YAML\n\tviper.AutomaticEnv()            // 读取匹配的环境变量\n\tviper.SetEnvPrefix(\"APISERVER\") // 读取环境变量的前缀为APISERVER\n\treplacer := strings.NewReplacer(\".\", \"_\")\n\tviper.SetEnvKeyReplacer(replacer)\n\tif err := viper.ReadInConfig(); err != nil { // viper解析配置文件\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *Config) initLog() {\n\tpassLagerCfg := log.PassLagerCfg{\n\t\tWriters:        viper.GetString(\"log.writers\"),\n\t\tLoggerLevel:    viper.GetString(\"log.logger_level\"),\n\t\tLoggerFile:     viper.GetString(\"log.logger_file\"),\n\t\tLogFormatText:  viper.GetBool(\"log.log_format_text\"),\n\t\tRollingPolicy:  viper.GetString(\"log.rollingPolicy\"),\n\t\tLogRotateDate:  viper.GetInt(\"log.log_rotate_date\"),\n\t\tLogRotateSize:  viper.GetInt(\"log.log_rotate_size\"),\n\t\tLogBackupCount: viper.GetInt(\"log.log_backup_count\"),\n\t}\n\n\tlog.InitWithConfig(&passLagerCfg)\n}\n\n// 监控配置文件变化并热加载程序\nfunc (c *Config) watchConfig() {\n\tviper.WatchConfig()\n\tviper.OnConfigChange(func(e fsnotify.Event) {\n\t\tlog.Infof(\"Config file changed: %s\", e.Name)\n\t})\n}\n"
  },
  {
    "path": "demo17/db.sql",
    "content": "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n/*!40101 SET NAMES utf8 */;\n/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;\n/*!40103 SET TIME_ZONE='+00:00' */;\n/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;\n/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n\nCREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */;\n\nUSE `db_apiserver`;\n\n--\n-- Table structure for table `tb_users`\n--\n\nDROP TABLE IF EXISTS `tb_users`;\n/*!40101 SET @saved_cs_client     = @@character_set_client */;\n/*!40101 SET character_set_client = utf8 */;\nCREATE TABLE `tb_users` (\n  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n  `username` varchar(255) NOT NULL,\n  `password` varchar(255) NOT NULL,\n  `createdAt` timestamp NULL DEFAULT NULL,\n  `updatedAt` timestamp NULL DEFAULT NULL,\n  `deletedAt` timestamp NULL DEFAULT NULL,\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `username` (`username`),\n  KEY `idx_tb_users_deletedAt` (`deletedAt`)\n) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;\n/*!40101 SET character_set_client = @saved_cs_client */;\n\n--\n-- Dumping data for table `tb_users`\n--\n\nLOCK TABLES `tb_users` WRITE;\n/*!40000 ALTER TABLE `tb_users` DISABLE KEYS */;\nINSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL);\n/*!40000 ALTER TABLE `tb_users` ENABLE KEYS */;\nUNLOCK TABLES;\n/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;\n\n/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;\n/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;\n/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n\n-- Dump completed on 2018-05-28  0:25:41\n"
  },
  {
    "path": "demo17/docs/docs.go",
    "content": "// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT\n// This file was generated by swaggo/swag at\n// 2018-06-18 02:15:43.723659228 +0800 CST m=+0.040196270\n\npackage docs\n\nimport (\n\t\"github.com/swaggo/swag\"\n)\n\nvar doc = `{\n    \"swagger\": \"2.0\",\n    \"info\": {\n        \"description\": \"apiserver demo\",\n        \"title\": \"Apiserver Example API\",\n        \"contact\": {\n            \"name\": \"lkong\",\n            \"url\": \"http://www.swagger.io/support\",\n            \"email\": \"466701708@qq.com\"\n        },\n        \"license\": {},\n        \"version\": \"1.0\"\n    },\n    \"host\": \"localhost:8080\",\n    \"basePath\": \"/v1\",\n    \"paths\": {\n        \"/login\": {\n            \"post\": {\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"summary\": \"Login generates the authentication token\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Username\",\n                        \"name\": \"username\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"object\"\n                        }\n                    },\n                    {\n                        \"description\": \"Password\",\n                        \"name\": \"password\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"object\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"code\\\":0,\\\"message\\\":\\\"OK\\\",\\\"data\\\":{\\\"token\\\":\\\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MjgwMTY5MjIsImlkIjowLCJuYmYiOjE1MjgwMTY5MjIsInVzZXJuYW1lIjoiYWRtaW4ifQ.LjxrK9DuAwAzUD8-9v43NzWBN7HXsSLfebw92DKd1JQ\\\"}}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/sd/cpu\": {\n            \"get\": {\n                \"description\": \"Checks the cpu usage\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"sd\"\n                ],\n                \"summary\": \"Checks the cpu usage\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"CRITICAL - Load average: 1.78, 1.99, 2.02 | Cores: 2\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/sd/disk\": {\n            \"get\": {\n                \"description\": \"Checks the disk usage\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"sd\"\n                ],\n                \"summary\": \"Checks the disk usage\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK - Free space: 17233MB (16GB) / 51200MB (50GB) | Used: 33%\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/sd/health\": {\n            \"get\": {\n                \"description\": \"Shows OK as the ping-pong result\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"sd\"\n                ],\n                \"summary\": \"Shows OK as the ping-pong result\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/sd/ram\": {\n            \"get\": {\n                \"description\": \"Checks the ram usage\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"sd\"\n                ],\n                \"summary\": \"Checks the ram usage\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK - Free space: 402MB (0GB) / 8192MB (8GB) | Used: 4%\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/user\": {\n            \"get\": {\n                \"description\": \"List users\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"user\"\n                ],\n                \"summary\": \"List the users in the database\",\n                \"parameters\": [\n                    {\n                        \"description\": \"List users\",\n                        \"name\": \"user\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"object\",\n                            \"$ref\": \"#/definitions/user.ListRequest\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"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\\\"}]}}\",\n                        \"schema\": {\n                            \"type\": \"object\",\n                            \"$ref\": \"#/definitions/user.SwaggerListResponse\"\n                        }\n                    }\n                }\n            },\n            \"post\": {\n                \"description\": \"Add a new user\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"user\"\n                ],\n                \"summary\": \"Add new user to the database\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Create a new user\",\n                        \"name\": \"user\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"object\",\n                            \"$ref\": \"#/definitions/user.CreateRequest\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"code\\\":0,\\\"message\\\":\\\"OK\\\",\\\"data\\\":{\\\"username\\\":\\\"kong\\\"}}\",\n                        \"schema\": {\n                            \"type\": \"object\",\n                            \"$ref\": \"#/definitions/user.CreateResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/{id}\": {\n            \"put\": {\n                \"description\": \"Update a user by ID\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"user\"\n                ],\n                \"summary\": \"Update a user info by the user identifier\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"The user's database id index num\",\n                        \"name\": \"id\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    },\n                    {\n                        \"description\": \"The user info\",\n                        \"name\": \"user\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"object\",\n                            \"$ref\": \"#/definitions/model.UserModel\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"code\\\":0,\\\"message\\\":\\\"OK\\\",\\\"data\\\":null}\",\n                        \"schema\": {\n                            \"type\": \"object\",\n                            \"$ref\": \"#/definitions/handler.Response\"\n                        }\n                    }\n                }\n            },\n            \"delete\": {\n                \"description\": \"Delete user by ID\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"user\"\n                ],\n                \"summary\": \"Delete an user by the user identifier\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"The user's database id index num\",\n                        \"name\": \"id\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"code\\\":0,\\\"message\\\":\\\"OK\\\",\\\"data\\\":null}\",\n                        \"schema\": {\n                            \"type\": \"object\",\n                            \"$ref\": \"#/definitions/handler.Response\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/{username}\": {\n            \"get\": {\n                \"description\": \"Get an user by username\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"user\"\n                ],\n                \"summary\": \"Get an user by the user identifier\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"Username\",\n                        \"name\": \"username\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"code\\\":0,\\\"message\\\":\\\"OK\\\",\\\"data\\\":{\\\"username\\\":\\\"kong\\\",\\\"password\\\":\\\"$2a$10$E0kwtmtLZbwW/bDQ8qI8e.eHPqhQOW9tvjwpyo/p05f/f4Qvr3OmS\\\"}}\",\n                        \"schema\": {\n                            \"type\": \"object\",\n                            \"$ref\": \"#/definitions/model.UserModel\"\n                        }\n                    }\n                }\n            }\n        }\n    },\n    \"definitions\": {\n        \"handler.Response\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"code\": {\n                    \"type\": \"integer\"\n                },\n                \"data\": {\n                    \"type\": \"object\"\n                },\n                \"message\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"model.UserModel\": {\n            \"type\": \"object\",\n            \"required\": [\n                \"username\",\n                \"password\"\n            ],\n            \"properties\": {\n                \"password\": {\n                    \"type\": \"string\"\n                },\n                \"username\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"user.CreateRequest\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"password\": {\n                    \"type\": \"string\"\n                },\n                \"username\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"user.CreateResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"username\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"user.ListRequest\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"limit\": {\n                    \"type\": \"integer\"\n                },\n                \"offset\": {\n                    \"type\": \"integer\"\n                },\n                \"username\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"user.SwaggerListResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"totalCount\": {\n                    \"type\": \"integer\"\n                },\n                \"userList\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"\\u0026{model UserInfo}\"\n                    }\n                }\n            }\n        }\n    }\n}`\n\ntype s struct{}\n\nfunc (s *s) ReadDoc() string {\n\treturn doc\n}\nfunc init() {\n\tswag.Register(swag.Name, &s{})\n}\n"
  },
  {
    "path": "demo17/docs/swagger/swagger.json",
    "content": "{\n    \"swagger\": \"2.0\",\n    \"info\": {\n        \"description\": \"apiserver demo\",\n        \"title\": \"Apiserver Example API\",\n        \"contact\": {\n            \"name\": \"lkong\",\n            \"url\": \"http://www.swagger.io/support\",\n            \"email\": \"466701708@qq.com\"\n        },\n        \"license\": {},\n        \"version\": \"1.0\"\n    },\n    \"host\": \"localhost:8080\",\n    \"basePath\": \"/v1\",\n    \"paths\": {\n        \"/login\": {\n            \"post\": {\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"summary\": \"Login generates the authentication token\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Username\",\n                        \"name\": \"username\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"object\"\n                        }\n                    },\n                    {\n                        \"description\": \"Password\",\n                        \"name\": \"password\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"object\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"code\\\":0,\\\"message\\\":\\\"OK\\\",\\\"data\\\":{\\\"token\\\":\\\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MjgwMTY5MjIsImlkIjowLCJuYmYiOjE1MjgwMTY5MjIsInVzZXJuYW1lIjoiYWRtaW4ifQ.LjxrK9DuAwAzUD8-9v43NzWBN7HXsSLfebw92DKd1JQ\\\"}}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/sd/cpu\": {\n            \"get\": {\n                \"description\": \"Checks the cpu usage\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"sd\"\n                ],\n                \"summary\": \"Checks the cpu usage\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"CRITICAL - Load average: 1.78, 1.99, 2.02 | Cores: 2\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/sd/disk\": {\n            \"get\": {\n                \"description\": \"Checks the disk usage\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"sd\"\n                ],\n                \"summary\": \"Checks the disk usage\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK - Free space: 17233MB (16GB) / 51200MB (50GB) | Used: 33%\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/sd/health\": {\n            \"get\": {\n                \"description\": \"Shows OK as the ping-pong result\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"sd\"\n                ],\n                \"summary\": \"Shows OK as the ping-pong result\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/sd/ram\": {\n            \"get\": {\n                \"description\": \"Checks the ram usage\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"sd\"\n                ],\n                \"summary\": \"Checks the ram usage\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK - Free space: 402MB (0GB) / 8192MB (8GB) | Used: 4%\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/user\": {\n            \"get\": {\n                \"description\": \"List users\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"user\"\n                ],\n                \"summary\": \"List the users in the database\",\n                \"parameters\": [\n                    {\n                        \"description\": \"List users\",\n                        \"name\": \"user\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"object\",\n                            \"$ref\": \"#/definitions/user.ListRequest\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"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\\\"}]}}\",\n                        \"schema\": {\n                            \"type\": \"object\",\n                            \"$ref\": \"#/definitions/user.SwaggerListResponse\"\n                        }\n                    }\n                }\n            },\n            \"post\": {\n                \"description\": \"Add a new user\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"user\"\n                ],\n                \"summary\": \"Add new user to the database\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Create a new user\",\n                        \"name\": \"user\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"object\",\n                            \"$ref\": \"#/definitions/user.CreateRequest\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"code\\\":0,\\\"message\\\":\\\"OK\\\",\\\"data\\\":{\\\"username\\\":\\\"kong\\\"}}\",\n                        \"schema\": {\n                            \"type\": \"object\",\n                            \"$ref\": \"#/definitions/user.CreateResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/{id}\": {\n            \"put\": {\n                \"description\": \"Update a user by ID\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"user\"\n                ],\n                \"summary\": \"Update a user info by the user identifier\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"The user's database id index num\",\n                        \"name\": \"id\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    },\n                    {\n                        \"description\": \"The user info\",\n                        \"name\": \"user\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"object\",\n                            \"$ref\": \"#/definitions/model.UserModel\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"code\\\":0,\\\"message\\\":\\\"OK\\\",\\\"data\\\":null}\",\n                        \"schema\": {\n                            \"type\": \"object\",\n                            \"$ref\": \"#/definitions/handler.Response\"\n                        }\n                    }\n                }\n            },\n            \"delete\": {\n                \"description\": \"Delete user by ID\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"user\"\n                ],\n                \"summary\": \"Delete an user by the user identifier\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"The user's database id index num\",\n                        \"name\": \"id\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"code\\\":0,\\\"message\\\":\\\"OK\\\",\\\"data\\\":null}\",\n                        \"schema\": {\n                            \"type\": \"object\",\n                            \"$ref\": \"#/definitions/handler.Response\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/{username}\": {\n            \"get\": {\n                \"description\": \"Get an user by username\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"user\"\n                ],\n                \"summary\": \"Get an user by the user identifier\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"Username\",\n                        \"name\": \"username\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"code\\\":0,\\\"message\\\":\\\"OK\\\",\\\"data\\\":{\\\"username\\\":\\\"kong\\\",\\\"password\\\":\\\"$2a$10$E0kwtmtLZbwW/bDQ8qI8e.eHPqhQOW9tvjwpyo/p05f/f4Qvr3OmS\\\"}}\",\n                        \"schema\": {\n                            \"type\": \"object\",\n                            \"$ref\": \"#/definitions/model.UserModel\"\n                        }\n                    }\n                }\n            }\n        }\n    },\n    \"definitions\": {\n        \"handler.Response\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"code\": {\n                    \"type\": \"integer\"\n                },\n                \"data\": {\n                    \"type\": \"object\"\n                },\n                \"message\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"model.UserModel\": {\n            \"type\": \"object\",\n            \"required\": [\n                \"username\",\n                \"password\"\n            ],\n            \"properties\": {\n                \"password\": {\n                    \"type\": \"string\"\n                },\n                \"username\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"user.CreateRequest\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"password\": {\n                    \"type\": \"string\"\n                },\n                \"username\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"user.CreateResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"username\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"user.ListRequest\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"limit\": {\n                    \"type\": \"integer\"\n                },\n                \"offset\": {\n                    \"type\": \"integer\"\n                },\n                \"username\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"user.SwaggerListResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"totalCount\": {\n                    \"type\": \"integer\"\n                },\n                \"userList\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"\\u0026{model UserInfo}\"\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "demo17/docs/swagger/swagger.yaml",
    "content": "basePath: /v1\ndefinitions:\n  handler.Response:\n    properties:\n      code:\n        type: integer\n      data:\n        type: object\n      message:\n        type: string\n    type: object\n  model.UserModel:\n    properties:\n      password:\n        type: string\n      username:\n        type: string\n    required:\n    - username\n    - password\n    type: object\n  user.CreateRequest:\n    properties:\n      password:\n        type: string\n      username:\n        type: string\n    type: object\n  user.CreateResponse:\n    properties:\n      username:\n        type: string\n    type: object\n  user.ListRequest:\n    properties:\n      limit:\n        type: integer\n      offset:\n        type: integer\n      username:\n        type: string\n    type: object\n  user.SwaggerListResponse:\n    properties:\n      totalCount:\n        type: integer\n      userList:\n        items:\n          type: '&{model UserInfo}'\n        type: array\n    type: object\nhost: localhost:8080\ninfo:\n  contact:\n    email: 466701708@qq.com\n    name: lkong\n    url: http://www.swagger.io/support\n  description: apiserver demo\n  license: {}\n  title: Apiserver Example API\n  version: \"1.0\"\npaths:\n  /login:\n    post:\n      parameters:\n      - description: Username\n        in: body\n        name: username\n        required: true\n        schema:\n          type: object\n      - description: Password\n        in: body\n        name: password\n        required: true\n        schema:\n          type: object\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: '{\"code\":0,\"message\":\"OK\",\"data\":{\"token\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MjgwMTY5MjIsImlkIjowLCJuYmYiOjE1MjgwMTY5MjIsInVzZXJuYW1lIjoiYWRtaW4ifQ.LjxrK9DuAwAzUD8-9v43NzWBN7HXsSLfebw92DKd1JQ\"}}'\n          schema:\n            type: string\n      summary: Login generates the authentication token\n  /sd/cpu:\n    get:\n      consumes:\n      - application/json\n      description: Checks the cpu usage\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 'CRITICAL - Load average: 1.78, 1.99, 2.02 | Cores: 2'\n          schema:\n            type: string\n      summary: Checks the cpu usage\n      tags:\n      - sd\n  /sd/disk:\n    get:\n      consumes:\n      - application/json\n      description: Checks the disk usage\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 'OK - Free space: 17233MB (16GB) / 51200MB (50GB) | Used: 33%'\n          schema:\n            type: string\n      summary: Checks the disk usage\n      tags:\n      - sd\n  /sd/health:\n    get:\n      consumes:\n      - application/json\n      description: Shows OK as the ping-pong result\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: OK\n          schema:\n            type: string\n      summary: Shows OK as the ping-pong result\n      tags:\n      - sd\n  /sd/ram:\n    get:\n      consumes:\n      - application/json\n      description: Checks the ram usage\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 'OK - Free space: 402MB (0GB) / 8192MB (8GB) | Used: 4%'\n          schema:\n            type: string\n      summary: Checks the ram usage\n      tags:\n      - sd\n  /user:\n    get:\n      consumes:\n      - application/json\n      description: List users\n      parameters:\n      - description: List users\n        in: body\n        name: user\n        required: true\n        schema:\n          $ref: '#/definitions/user.ListRequest'\n          type: object\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: '{\"code\":0,\"message\":\"OK\",\"data\":{\"totalCount\":1,\"userList\":[{\"id\":0,\"username\":\"admin\",\"random\":\"user\n            ''admin'' get random string ''EnqntiSig''\",\"password\":\"$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG\",\"createdAt\":\"2018-05-28\n            00:25:33\",\"updatedAt\":\"2018-05-28 00:25:33\"}]}}'\n          schema:\n            $ref: '#/definitions/user.SwaggerListResponse'\n            type: object\n      summary: List the users in the database\n      tags:\n      - user\n    post:\n      consumes:\n      - application/json\n      description: Add a new user\n      parameters:\n      - description: Create a new user\n        in: body\n        name: user\n        required: true\n        schema:\n          $ref: '#/definitions/user.CreateRequest'\n          type: object\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: '{\"code\":0,\"message\":\"OK\",\"data\":{\"username\":\"kong\"}}'\n          schema:\n            $ref: '#/definitions/user.CreateResponse'\n            type: object\n      summary: Add new user to the database\n      tags:\n      - user\n  /user/{id}:\n    delete:\n      consumes:\n      - application/json\n      description: Delete user by ID\n      parameters:\n      - description: The user's database id index num\n        in: path\n        name: id\n        required: true\n        type: integer\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: '{\"code\":0,\"message\":\"OK\",\"data\":null}'\n          schema:\n            $ref: '#/definitions/handler.Response'\n            type: object\n      summary: Delete an user by the user identifier\n      tags:\n      - user\n    put:\n      consumes:\n      - application/json\n      description: Update a user by ID\n      parameters:\n      - description: The user's database id index num\n        in: path\n        name: id\n        required: true\n        type: integer\n      - description: The user info\n        in: body\n        name: user\n        required: true\n        schema:\n          $ref: '#/definitions/model.UserModel'\n          type: object\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: '{\"code\":0,\"message\":\"OK\",\"data\":null}'\n          schema:\n            $ref: '#/definitions/handler.Response'\n            type: object\n      summary: Update a user info by the user identifier\n      tags:\n      - user\n  /user/{username}:\n    get:\n      consumes:\n      - application/json\n      description: Get an user by username\n      parameters:\n      - description: Username\n        in: path\n        name: username\n        required: true\n        type: string\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: '{\"code\":0,\"message\":\"OK\",\"data\":{\"username\":\"kong\",\"password\":\"$2a$10$E0kwtmtLZbwW/bDQ8qI8e.eHPqhQOW9tvjwpyo/p05f/f4Qvr3OmS\"}}'\n          schema:\n            $ref: '#/definitions/model.UserModel'\n            type: object\n      summary: Get an user by the user identifier\n      tags:\n      - user\nswagger: \"2.0\"\n"
  },
  {
    "path": "demo17/handler/handler.go",
    "content": "package handler\n\nimport (\n\t\"net/http\"\n\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype Response struct {\n\tCode    int         `json:\"code\"`\n\tMessage string      `json:\"message\"`\n\tData    interface{} `json:\"data\"`\n}\n\nfunc SendResponse(c *gin.Context, err error, data interface{}) {\n\tcode, message := errno.DecodeErr(err)\n\n\t// always return http.StatusOK\n\tc.JSON(http.StatusOK, Response{\n\t\tCode:    code,\n\t\tMessage: message,\n\t\tData:    data,\n\t})\n}\n"
  },
  {
    "path": "demo17/handler/sd/check.go",
    "content": "package sd\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/shirou/gopsutil/cpu\"\n\t\"github.com/shirou/gopsutil/disk\"\n\t\"github.com/shirou/gopsutil/load\"\n\t\"github.com/shirou/gopsutil/mem\"\n)\n\nconst (\n\tB  = 1\n\tKB = 1024 * B\n\tMB = 1024 * KB\n\tGB = 1024 * MB\n)\n\n// @Summary Shows OK as the ping-pong result\n// @Description Shows OK as the ping-pong result\n// @Tags sd\n// @Accept  json\n// @Produce  json\n// @Success 200 {string} plain \"OK\"\n// @Router /sd/health [get]\nfunc HealthCheck(c *gin.Context) {\n\tmessage := \"OK\"\n\tc.String(http.StatusOK, \"\\n\"+message)\n}\n\n// @Summary Checks the disk usage\n// @Description Checks the disk usage\n// @Tags sd\n// @Accept  json\n// @Produce  json\n// @Success 200 {string} plain \"OK - Free space: 17233MB (16GB) / 51200MB (50GB) | Used: 33%\"\n// @Router /sd/disk [get]\nfunc DiskCheck(c *gin.Context) {\n\tu, _ := disk.Usage(\"/\")\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusOK\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n\n// @Summary Checks the cpu usage\n// @Description Checks the cpu usage\n// @Tags sd\n// @Accept  json\n// @Produce  json\n// @Success 200 {string} plain \"CRITICAL - Load average: 1.78, 1.99, 2.02 | Cores: 2\"\n// @Router /sd/cpu [get]\nfunc CPUCheck(c *gin.Context) {\n\tcores, _ := cpu.Counts(false)\n\n\ta, _ := load.Avg()\n\tl1 := a.Load1\n\tl5 := a.Load5\n\tl15 := a.Load15\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif l5 >= float64(cores-1) {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if l5 >= float64(cores-2) {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Load average: %.2f, %.2f, %.2f | Cores: %d\", text, l1, l5, l15, cores)\n\tc.String(status, \"\\n\"+message)\n}\n\n// @Summary Checks the ram usage\n// @Description Checks the ram usage\n// @Tags sd\n// @Accept  json\n// @Produce  json\n// @Success 200 {string} plain \"OK - Free space: 402MB (0GB) / 8192MB (8GB) | Used: 4%\"\n// @Router /sd/ram [get]\nfunc RAMCheck(c *gin.Context) {\n\tu, _ := mem.VirtualMemory()\n\n\tusedMB := int(u.Used) / MB\n\tusedGB := int(u.Used) / GB\n\ttotalMB := int(u.Total) / MB\n\ttotalGB := int(u.Total) / GB\n\tusedPercent := int(u.UsedPercent)\n\n\tstatus := http.StatusOK\n\ttext := \"OK\"\n\n\tif usedPercent >= 95 {\n\t\tstatus = http.StatusInternalServerError\n\t\ttext = \"CRITICAL\"\n\t} else if usedPercent >= 90 {\n\t\tstatus = http.StatusTooManyRequests\n\t\ttext = \"WARNING\"\n\t}\n\n\tmessage := fmt.Sprintf(\"%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%\", text, usedMB, usedGB, totalMB, totalGB, usedPercent)\n\tc.String(status, \"\\n\"+message)\n}\n"
  },
  {
    "path": "demo17/handler/user/create.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// @Summary Add new user to the database\n// @Description Add a new user\n// @Tags user\n// @Accept  json\n// @Produce  json\n// @Param user body user.CreateRequest true \"Create a new user\"\n// @Success 200 {object} user.CreateResponse \"{\"code\":0,\"message\":\"OK\",\"data\":{\"username\":\"kong\"}}\"\n// @Router /user [post]\nfunc Create(c *gin.Context) {\n\tlog.Info(\"User Create function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\tvar r CreateRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tu := model.UserModel{\n\t\tUsername: r.Username,\n\t\tPassword: r.Password,\n\t}\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\t// Insert the user to the database.\n\tif err := u.Create(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\trsp := CreateResponse{\n\t\tUsername: r.Username,\n\t}\n\n\t// Show the user information.\n\tSendResponse(c, nil, rsp)\n}\n"
  },
  {
    "path": "demo17/handler/user/delete.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// @Summary Delete an user by the user identifier\n// @Description Delete user by ID\n// @Tags user\n// @Accept  json\n// @Produce  json\n// @Param id path uint64 true \"The user's database id index num\"\n// @Success 200 {object} handler.Response \"{\"code\":0,\"message\":\"OK\",\"data\":null}\"\n// @Router /user/{id} [delete]\nfunc Delete(c *gin.Context) {\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\tif err := model.DeleteUser(uint64(userId)); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo17/handler/user/get.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// @Summary Get an user by the user identifier\n// @Description Get an user by username\n// @Tags user\n// @Accept  json\n// @Produce  json\n// @Param username path string true \"Username\"\n// @Success 200 {object} model.UserModel \"{\"code\":0,\"message\":\"OK\",\"data\":{\"username\":\"kong\",\"password\":\"$2a$10$E0kwtmtLZbwW/bDQ8qI8e.eHPqhQOW9tvjwpyo/p05f/f4Qvr3OmS\"}}\"\n// @Router /user/{username} [get]\nfunc Get(c *gin.Context) {\n\tusername := c.Param(\"username\")\n\t// Get the user by the `username` from the database.\n\tuser, err := model.GetUser(username)\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrUserNotFound, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, user)\n}\n"
  },
  {
    "path": "demo17/handler/user/list.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/service\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n)\n\n// @Summary List the users in the database\n// @Description List users\n// @Tags user\n// @Accept  json\n// @Produce  json\n// @Param user body user.ListRequest true \"List users\"\n// @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\"}]}}\"\n// @Router /user [get]\nfunc List(c *gin.Context) {\n\tlog.Info(\"List function called.\")\n\tvar r ListRequest\n\tif err := c.Bind(&r); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\tinfos, count, err := service.ListUser(r.Username, r.Offset, r.Limit)\n\tif err != nil {\n\t\tSendResponse(c, err, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, ListResponse{\n\t\tTotalCount: count,\n\t\tUserList:   infos,\n\t})\n}\n"
  },
  {
    "path": "demo17/handler/user/login.go",
    "content": "package user\n\nimport (\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/auth\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/pkg/token\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// @Summary Login generates the authentication token\n// @Produce  json\n// @Param username body string true \"Username\"\n// @Param password body string true \"Password\"\n// @Success 200 {string} json \"{\"code\":0,\"message\":\"OK\",\"data\":{\"token\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MjgwMTY5MjIsImlkIjowLCJuYmYiOjE1MjgwMTY5MjIsInVzZXJuYW1lIjoiYWRtaW4ifQ.LjxrK9DuAwAzUD8-9v43NzWBN7HXsSLfebw92DKd1JQ\"}}\"\n// @Router /login [post]\nfunc Login(c *gin.Context) {\n\t// Binding the data with the user struct.\n\tvar u model.UserModel\n\tif err := c.Bind(&u); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\t// Get the user information by the login username.\n\td, err := model.GetUser(u.Username)\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrUserNotFound, nil)\n\t\treturn\n\t}\n\n\t// Compare the login password with the user password.\n\tif err := auth.Compare(d.Password, u.Password); err != nil {\n\t\tSendResponse(c, errno.ErrPasswordIncorrect, nil)\n\t\treturn\n\t}\n\n\t// Sign the json web token.\n\tt, err := token.Sign(c, token.Context{ID: d.Id, Username: d.Username}, \"\")\n\tif err != nil {\n\t\tSendResponse(c, errno.ErrToken, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, model.Token{Token: t})\n}\n"
  },
  {
    "path": "demo17/handler/user/update.go",
    "content": "package user\n\nimport (\n\t\"strconv\"\n\n\t. \"apiserver/handler\"\n\t\"apiserver/model\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/util\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/lexkong/log/lager\"\n)\n\n// @Summary Update a user info by the user identifier\n// @Description Update a user by ID\n// @Tags user\n// @Accept  json\n// @Produce  json\n// @Param id path uint64 true \"The user's database id index num\"\n// @Param user body model.UserModel true \"The user info\"\n// @Success 200 {object} handler.Response \"{\"code\":0,\"message\":\"OK\",\"data\":null}\"\n// @Router /user/{id} [put]\nfunc Update(c *gin.Context) {\n\tlog.Info(\"Update function called.\", lager.Data{\"X-Request-Id\": util.GetReqID(c)})\n\t// Get the user id from the url parameter.\n\tuserId, _ := strconv.Atoi(c.Param(\"id\"))\n\n\t// Binding the user data.\n\tvar u model.UserModel\n\tif err := c.Bind(&u); err != nil {\n\t\tSendResponse(c, errno.ErrBind, nil)\n\t\treturn\n\t}\n\n\t// We update the record based on the user id.\n\tu.Id = uint64(userId)\n\n\t// Validate the data.\n\tif err := u.Validate(); err != nil {\n\t\tSendResponse(c, errno.ErrValidation, nil)\n\t\treturn\n\t}\n\n\t// Encrypt the user password.\n\tif err := u.Encrypt(); err != nil {\n\t\tSendResponse(c, errno.ErrEncrypt, nil)\n\t\treturn\n\t}\n\n\t// Save changed fields.\n\tif err := u.Update(); err != nil {\n\t\tSendResponse(c, errno.ErrDatabase, nil)\n\t\treturn\n\t}\n\n\tSendResponse(c, nil, nil)\n}\n"
  },
  {
    "path": "demo17/handler/user/user.go",
    "content": "package user\n\nimport (\n\t\"apiserver/model\"\n)\n\ntype CreateRequest struct {\n\tUsername string `json:\"username\"`\n\tPassword string `json:\"password\"`\n}\n\ntype CreateResponse struct {\n\tUsername string `json:\"username\"`\n}\n\ntype ListRequest struct {\n\tUsername string `json:\"username\"`\n\tOffset   int    `json:\"offset\"`\n\tLimit    int    `json:\"limit\"`\n}\n\ntype ListResponse struct {\n\tTotalCount uint64            `json:\"totalCount\"`\n\tUserList   []*model.UserInfo `json:\"userList\"`\n}\n\ntype SwaggerListResponse struct {\n\tTotalCount uint64           `json:\"totalCount\"`\n\tUserList   []model.UserInfo `json:\"userList\"`\n}\n"
  },
  {
    "path": "demo17/main.go",
    "content": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t_ \"net/http/pprof\"\n\t\"os\"\n\t\"time\"\n\n\t\"apiserver/config\"\n\t\"apiserver/model\"\n\tv \"apiserver/pkg/version\"\n\t\"apiserver/router\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tcfg     = pflag.StringP(\"config\", \"c\", \"\", \"apiserver config file path.\")\n\tversion = pflag.BoolP(\"version\", \"v\", false, \"show version info.\")\n)\n\n// @title Apiserver Example API\n// @version 1.0\n// @description apiserver demo\n\n// @contact.name lkong\n// @contact.url http://www.swagger.io/support\n// @contact.email 466701708@qq.com\n\n// @host localhost:8080\n// @BasePath /v1\nfunc main() {\n\tpflag.Parse()\n\tif *version {\n\t\tv := v.Get()\n\t\tmarshalled, err := json.MarshalIndent(&v, \"\", \"  \")\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"%v\\n\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tfmt.Println(string(marshalled))\n\t\treturn\n\t}\n\n\t// init config\n\tif err := config.Init(*cfg); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// init db\n\tmodel.DB.Init()\n\tdefer model.DB.Close()\n\n\t// Set gin mode.\n\tgin.SetMode(viper.GetString(\"runmode\"))\n\n\t// Create the Gin engine.\n\tg := gin.New()\n\n\t// Routes.\n\trouter.Load(\n\t\t// Cores.\n\t\tg,\n\n\t\t// Middlwares.\n\t\tmiddleware.Logging(),\n\t\tmiddleware.RequestId(),\n\t)\n\n\t// Ping the server to make sure the router is working.\n\tgo func() {\n\t\tif err := pingServer(); err != nil {\n\t\t\tlog.Fatal(\"The router has no response, or it might took too long to start up.\", err)\n\t\t}\n\t\tlog.Info(\"The router has been deployed successfully.\")\n\t}()\n\n\t// Start to listening the incoming requests.\n\tcert := viper.GetString(\"tls.cert\")\n\tkey := viper.GetString(\"tls.key\")\n\tif cert != \"\" && key != \"\" {\n\t\tgo func() {\n\t\t\tlog.Infof(\"Start to listening the incoming requests on https address: %s\", viper.GetString(\"tls.addr\"))\n\t\t\tlog.Info(http.ListenAndServeTLS(viper.GetString(\"tls.addr\"), cert, key, g).Error())\n\t\t}()\n\t}\n\n\tlog.Infof(\"Start to listening the incoming requests on http address: %s\", viper.GetString(\"addr\"))\n\tlog.Info(http.ListenAndServe(viper.GetString(\"addr\"), g).Error())\n}\n\n// pingServer pings the http server to make sure the router is working.\nfunc pingServer() error {\n\tfor i := 0; i < viper.GetInt(\"max_ping_count\"); i++ {\n\t\t// Ping the server by sending a GET request to `/health`.\n\t\tresp, err := http.Get(viper.GetString(\"url\") + \"/sd/health\")\n\t\tif err == nil && resp.StatusCode == 200 {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Sleep for a second to continue the next ping.\n\t\tlog.Info(\"Waiting for the router, retry in 1 second.\")\n\t\ttime.Sleep(time.Second)\n\t}\n\treturn errors.New(\"Cannot connect to the router.\")\n}\n"
  },
  {
    "path": "demo17/model/init.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/lexkong/log\"\n\t\"github.com/spf13/viper\"\n\t// MySQL driver.\n\t\"github.com/jinzhu/gorm\"\n\t_ \"github.com/jinzhu/gorm/dialects/mysql\"\n)\n\ntype Database struct {\n\tSelf   *gorm.DB\n\tDocker *gorm.DB\n}\n\nvar DB *Database\n\nfunc openDB(username, password, addr, name string) *gorm.DB {\n\tconfig := fmt.Sprintf(\"%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s\",\n\t\tusername,\n\t\tpassword,\n\t\taddr,\n\t\tname,\n\t\ttrue,\n\t\t//\"Asia/Shanghai\"),\n\t\t\"Local\")\n\n\tdb, err := gorm.Open(\"mysql\", config)\n\tif err != nil {\n\t\tlog.Errorf(err, \"Database connection failed. Database name: %s\", name)\n\t}\n\n\t// set for db connection\n\tsetupDB(db)\n\n\treturn db\n}\n\nfunc setupDB(db *gorm.DB) {\n\tdb.LogMode(viper.GetBool(\"gormlog\"))\n\t//db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数，默认值为0表示不限制.设置最大的连接数，可以避免并发太高导致连接mysql出现too many connections的错误。\n\tdb.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。\n}\n\n// used for cli\nfunc InitSelfDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"db.username\"),\n\t\tviper.GetString(\"db.password\"),\n\t\tviper.GetString(\"db.addr\"),\n\t\tviper.GetString(\"db.name\"))\n}\n\nfunc GetSelfDB() *gorm.DB {\n\treturn InitSelfDB()\n}\n\nfunc InitDockerDB() *gorm.DB {\n\treturn openDB(viper.GetString(\"docker_db.username\"),\n\t\tviper.GetString(\"docker_db.password\"),\n\t\tviper.GetString(\"docker_db.addr\"),\n\t\tviper.GetString(\"docker_db.name\"))\n}\n\nfunc GetDockerDB() *gorm.DB {\n\treturn InitDockerDB()\n}\n\nfunc (db *Database) Init() {\n\tDB = &Database{\n\t\tSelf:   GetSelfDB(),\n\t\tDocker: GetDockerDB(),\n\t}\n}\n\nfunc (db *Database) Close() {\n\tDB.Self.Close()\n\tDB.Docker.Close()\n}\n"
  },
  {
    "path": "demo17/model/model.go",
    "content": "package model\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype BaseModel struct {\n\tId        uint64     `gorm:\"primary_key;AUTO_INCREMENT;column:id\" json:\"-\"`\n\tCreatedAt time.Time  `gorm:\"column:createdAt\" json:\"-\"`\n\tUpdatedAt time.Time  `gorm:\"column:updatedAt\" json:\"-\"`\n\tDeletedAt *time.Time `gorm:\"column:deletedAt\" sql:\"index\" json:\"-\"`\n}\n\ntype UserInfo struct {\n\tId        uint64 `json:\"id\"`\n\tUsername  string `json:\"username\"`\n\tSayHello  string `json:\"sayHello\"`\n\tPassword  string `json:\"password\"`\n\tCreatedAt string `json:\"createdAt\"`\n\tUpdatedAt string `json:\"updatedAt\"`\n}\n\ntype UserList struct {\n\tLock  *sync.Mutex\n\tIdMap map[uint64]*UserInfo\n}\n\n// Token represents a JSON web token.\ntype Token struct {\n\tToken string `json:\"token\"`\n}\n"
  },
  {
    "path": "demo17/model/user.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"apiserver/pkg/auth\"\n\t\"apiserver/pkg/constvar\"\n\n\tvalidator \"gopkg.in/go-playground/validator.v9\"\n)\n\n// User represents a registered user.\ntype UserModel struct {\n\tBaseModel\n\tUsername string `json:\"username\" gorm:\"column:username;not null\" binding:\"required\" validate:\"min=1,max=32\"`\n\tPassword string `json:\"password\" gorm:\"column:password;not null\" binding:\"required\" validate:\"min=5,max=128\"`\n}\n\nfunc (c *UserModel) TableName() string {\n\treturn \"tb_users\"\n}\n\n// Create creates a new user account.\nfunc (u *UserModel) Create() error {\n\treturn DB.Self.Create(&u).Error\n}\n\n// DeleteUser deletes the user by the user identifier.\nfunc DeleteUser(id uint64) error {\n\tuser := UserModel{}\n\tuser.BaseModel.Id = id\n\treturn DB.Self.Delete(&user).Error\n}\n\n// Update updates an user account information.\nfunc (u *UserModel) Update() error {\n\treturn DB.Self.Save(u).Error\n}\n\n// GetUser gets an user by the user identifier.\nfunc GetUser(username string) (*UserModel, error) {\n\tu := &UserModel{}\n\td := DB.Self.Where(\"username = ?\", username).First(&u)\n\treturn u, d.Error\n}\n\n// ListUser List all users\nfunc ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) {\n\tif limit == 0 {\n\t\tlimit = constvar.DefaultLimit\n\t}\n\n\tusers := make([]*UserModel, 0)\n\tvar count uint64\n\n\twhere := fmt.Sprintf(\"username like '%%%s%%'\", username)\n\tif err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\tif err := DB.Self.Where(where).Offset(offset).Limit(limit).Order(\"id desc\").Find(&users).Error; err != nil {\n\t\treturn users, count, err\n\t}\n\n\treturn users, count, nil\n}\n\n// Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct).\nfunc (u *UserModel) Compare(pwd string) (err error) {\n\terr = auth.Compare(u.Password, pwd)\n\treturn\n}\n\n// Encrypt the user password.\nfunc (u *UserModel) Encrypt() (err error) {\n\tu.Password, err = auth.Encrypt(u.Password)\n\treturn\n}\n\n// Validate the fields.\nfunc (u *UserModel) Validate() error {\n\tvalidate := validator.New()\n\treturn validate.Struct(u)\n}\n"
  },
  {
    "path": "demo17/pkg/auth/auth.go",
    "content": "package auth\n\nimport \"golang.org/x/crypto/bcrypt\"\n\n// Encrypt encrypts the plain text with bcrypt.\nfunc Encrypt(source string) (string, error) {\n\thashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost)\n\treturn string(hashedBytes), err\n}\n\n// Compare compares the encrypted text with the plain text if it's the same.\nfunc Compare(hashedPassword, password string) error {\n\treturn bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))\n}\n"
  },
  {
    "path": "demo17/pkg/constvar/constvar.go",
    "content": "package constvar\n\nconst (\n\tDefaultLimit = 50\n)\n"
  },
  {
    "path": "demo17/pkg/errno/code.go",
    "content": "package errno\n\nvar (\n\t// Common errors\n\tOK                  = &Errno{Code: 0, Message: \"OK\"}\n\tInternalServerError = &Errno{Code: 10001, Message: \"Internal server error\"}\n\tErrBind             = &Errno{Code: 10002, Message: \"Error occurred while binding the request body to the struct.\"}\n\n\tErrValidation = &Errno{Code: 20001, Message: \"Validation failed.\"}\n\tErrDatabase   = &Errno{Code: 20002, Message: \"Database error.\"}\n\tErrToken      = &Errno{Code: 20003, Message: \"Error occurred while signing the JSON web token.\"}\n\n\t// user errors\n\tErrEncrypt           = &Errno{Code: 20101, Message: \"Error occurred while encrypting the user password.\"}\n\tErrUserNotFound      = &Errno{Code: 20102, Message: \"The user was not found.\"}\n\tErrTokenInvalid      = &Errno{Code: 20103, Message: \"The token was invalid.\"}\n\tErrPasswordIncorrect = &Errno{Code: 20104, Message: \"The password was incorrect.\"}\n)\n"
  },
  {
    "path": "demo17/pkg/errno/errno.go",
    "content": "package errno\n\nimport \"fmt\"\n\ntype Errno struct {\n\tCode    int\n\tMessage string\n}\n\nfunc (err Errno) Error() string {\n\treturn err.Message\n}\n\n// Err represents an error\ntype Err struct {\n\tCode    int\n\tMessage string\n\tErr     error\n}\n\nfunc New(errno *Errno, err error) *Err {\n\treturn &Err{Code: errno.Code, Message: errno.Message, Err: err}\n}\n\nfunc (err *Err) Add(message string) error {\n\terr.Message += \" \" + message\n\treturn err\n}\n\nfunc (err *Err) Addf(format string, args ...interface{}) error {\n\terr.Message += \" \" + fmt.Sprintf(format, args...)\n\treturn err\n}\n\nfunc (err *Err) Error() string {\n\treturn fmt.Sprintf(\"Err - code: %d, message: %s, error: %s\", err.Code, err.Message, err.Err)\n}\n\nfunc IsErrUserNotFound(err error) bool {\n\tcode, _ := DecodeErr(err)\n\treturn code == ErrUserNotFound.Code\n}\n\nfunc DecodeErr(err error) (int, string) {\n\tif err == nil {\n\t\treturn OK.Code, OK.Message\n\t}\n\n\tswitch typed := err.(type) {\n\tcase *Err:\n\t\treturn typed.Code, typed.Message\n\tcase *Errno:\n\t\treturn typed.Code, typed.Message\n\tdefault:\n\t}\n\n\treturn InternalServerError.Code, err.Error()\n}\n"
  },
  {
    "path": "demo17/pkg/token/token.go",
    "content": "package token\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\tjwt \"github.com/dgrijalva/jwt-go\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\t// ErrMissingHeader means the `Authorization` header was empty.\n\tErrMissingHeader = errors.New(\"The length of the `Authorization` header is zero.\")\n)\n\n// Context is the context of the JSON web token.\ntype Context struct {\n\tID       uint64\n\tUsername string\n}\n\n// secretFunc validates the secret format.\nfunc secretFunc(secret string) jwt.Keyfunc {\n\treturn func(token *jwt.Token) (interface{}, error) {\n\t\t// Make sure the `alg` is what we except.\n\t\tif _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {\n\t\t\treturn nil, jwt.ErrSignatureInvalid\n\t\t}\n\n\t\treturn []byte(secret), nil\n\t}\n}\n\n// Parse validates the token with the specified secret,\n// and returns the context if the token was valid.\nfunc Parse(tokenString string, secret string) (*Context, error) {\n\tctx := &Context{}\n\n\t// Parse the token.\n\ttoken, err := jwt.Parse(tokenString, secretFunc(secret))\n\n\t// Parse error.\n\tif err != nil {\n\t\treturn ctx, err\n\n\t\t// Read the token if it's valid.\n\t} else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {\n\t\tctx.ID = uint64(claims[\"id\"].(float64))\n\t\tctx.Username = claims[\"username\"].(string)\n\t\treturn ctx, nil\n\n\t\t// Other errors.\n\t} else {\n\t\treturn ctx, err\n\t}\n}\n\n// ParseRequest gets the token from the header and\n// pass it to the Parse function to parses the token.\nfunc ParseRequest(c *gin.Context) (*Context, error) {\n\theader := c.Request.Header.Get(\"Authorization\")\n\n\t// Load the jwt secret from config\n\tsecret := viper.GetString(\"jwt_secret\")\n\n\tif len(header) == 0 {\n\t\treturn &Context{}, ErrMissingHeader\n\t}\n\n\tvar t string\n\t// Parse the header to get the token part.\n\tfmt.Sscanf(header, \"Bearer %s\", &t)\n\treturn Parse(t, secret)\n}\n\n// Sign signs the context with the specified secret.\nfunc Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) {\n\t// Load the jwt secret from the Gin config if the secret isn't specified.\n\tif secret == \"\" {\n\t\tsecret = viper.GetString(\"jwt_secret\")\n\t}\n\t// The token content.\n\ttoken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{\n\t\t\"id\":       c.ID,\n\t\t\"username\": c.Username,\n\t\t\"nbf\":      time.Now().Unix(),\n\t\t\"iat\":      time.Now().Unix(),\n\t})\n\t// Sign the token with the specified secret.\n\ttokenString, err = token.SignedString([]byte(secret))\n\n\treturn\n}\n"
  },
  {
    "path": "demo17/pkg/version/base.go",
    "content": "package version\n\nvar (\n\tgitTag       string = \"\"\n\tgitCommit    string = \"$Format:%H$\"          // sha1 from git, output of $(git rev-parse HEAD)\n\tgitTreeState string = \"not a git tree\"       // state of git tree, either \"clean\" or \"dirty\"\n\tbuildDate    string = \"1970-01-01T00:00:00Z\" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')\n)\n"
  },
  {
    "path": "demo17/pkg/version/doc.go",
    "content": "package version\n"
  },
  {
    "path": "demo17/pkg/version/version.go",
    "content": "package version\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n)\n\n// Info contains versioning information.\ntype Info struct {\n\tGitTag       string `json:\"gitTag\"`\n\tGitCommit    string `json:\"gitCommit\"`\n\tGitTreeState string `json:\"gitTreeState\"`\n\tBuildDate    string `json:\"buildDate\"`\n\tGoVersion    string `json:\"goVersion\"`\n\tCompiler     string `json:\"compiler\"`\n\tPlatform     string `json:\"platform\"`\n}\n\n// String returns info as a human-friendly version string.\nfunc (info Info) String() string {\n\treturn info.GitTag\n}\n\nfunc Get() Info {\n\treturn Info{\n\t\tGitTag:       gitTag,\n\t\tGitCommit:    gitCommit,\n\t\tGitTreeState: gitTreeState,\n\t\tBuildDate:    buildDate,\n\t\tGoVersion:    runtime.Version(),\n\t\tCompiler:     runtime.Compiler,\n\t\tPlatform:     fmt.Sprintf(\"%s/%s\", runtime.GOOS, runtime.GOARCH),\n\t}\n}\n"
  },
  {
    "path": "demo17/router/middleware/auth.go",
    "content": "package middleware\n\nimport (\n\t\"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\t\"apiserver/pkg/token\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc AuthMiddleware() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// Parse the json web token.\n\t\tif _, err := token.ParseRequest(c); err != nil {\n\t\t\thandler.SendResponse(c, errno.ErrTokenInvalid, nil)\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "demo17/router/middleware/header.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// NoCache is a middleware function that appends headers\n// to prevent the client from caching the HTTP response.\nfunc NoCache(c *gin.Context) {\n\tc.Header(\"Cache-Control\", \"no-cache, no-store, max-age=0, must-revalidate, value\")\n\tc.Header(\"Expires\", \"Thu, 01 Jan 1970 00:00:00 GMT\")\n\tc.Header(\"Last-Modified\", time.Now().UTC().Format(http.TimeFormat))\n\tc.Next()\n}\n\n// Options is a middleware function that appends headers\n// for options requests and aborts then exits the middleware\n// chain and ends the request.\nfunc Options(c *gin.Context) {\n\tif c.Request.Method != \"OPTIONS\" {\n\t\tc.Next()\n\t} else {\n\t\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\t\tc.Header(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Access-Control-Allow-Headers\", \"authorization, origin, content-type, accept\")\n\t\tc.Header(\"Allow\", \"HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS\")\n\t\tc.Header(\"Content-Type\", \"application/json\")\n\t\tc.AbortWithStatus(200)\n\t}\n}\n\n// Secure is a middleware function that appends security\n// and resource access headers.\nfunc Secure(c *gin.Context) {\n\tc.Header(\"Access-Control-Allow-Origin\", \"*\")\n\tc.Header(\"X-Frame-Options\", \"DENY\")\n\tc.Header(\"X-Content-Type-Options\", \"nosniff\")\n\tc.Header(\"X-XSS-Protection\", \"1; mode=block\")\n\tif c.Request.TLS != nil {\n\t\tc.Header(\"Strict-Transport-Security\", \"max-age=31536000\")\n\t}\n\n\t// Also consider adding Content-Security-Policy headers\n\t// c.Header(\"Content-Security-Policy\", \"script-src 'self' https://cdnjs.cloudflare.com\")\n}\n"
  },
  {
    "path": "demo17/router/middleware/logging.go",
    "content": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"apiserver/handler\"\n\t\"apiserver/pkg/errno\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/lexkong/log\"\n\t\"github.com/willf/pad\"\n)\n\ntype bodyLogWriter struct {\n\tgin.ResponseWriter\n\tbody *bytes.Buffer\n}\n\nfunc (w bodyLogWriter) Write(b []byte) (int, error) {\n\tw.body.Write(b)\n\treturn w.ResponseWriter.Write(b)\n}\n\n// Logging is a middleware function that logs the each request.\nfunc Logging() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tstart := time.Now().UTC()\n\t\tpath := c.Request.URL.Path\n\n\t\treg := regexp.MustCompile(\"(/v1/user|/login)\")\n\t\tif !reg.MatchString(path) {\n\t\t\treturn\n\t\t}\n\n\t\t// Skip for the health check requests.\n\t\tif path == \"/sd/health\" || path == \"/sd/ram\" || path == \"/sd/cpu\" || path == \"/sd/disk\" {\n\t\t\treturn\n\t\t}\n\n\t\t// Read the Body content\n\t\tvar bodyBytes []byte\n\t\tif c.Request.Body != nil {\n\t\t\tbodyBytes, _ = ioutil.ReadAll(c.Request.Body)\n\t\t}\n\n\t\t// Restore the io.ReadCloser to its original state\n\t\tc.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))\n\n\t\t// The basic informations.\n\t\tmethod := c.Request.Method\n\t\tip := c.ClientIP()\n\n\t\t//log.Debugf(\"New request come in, path: %s, Method: %s, body `%s`\", path, method, string(bodyBytes))\n\t\tblw := &bodyLogWriter{\n\t\t\tbody:           bytes.NewBufferString(\"\"),\n\t\t\tResponseWriter: c.Writer,\n\t\t}\n\t\tc.Writer = blw\n\n\t\t// Continue.\n\t\tc.Next()\n\n\t\t// Calculates the latency.\n\t\tend := time.Now().UTC()\n\t\tlatency := end.Sub(start)\n\n\t\tcode, message := -1, \"\"\n\n\t\t// get code and message\n\t\tvar response handler.Response\n\t\tif err := json.Unmarshal(blw.body.Bytes(), &response); err != nil {\n\t\t\tlog.Errorf(err, \"response body can not unmarshal to model.Response struct, body: `%s`\", blw.body.Bytes())\n\t\t\tcode = errno.InternalServerError.Code\n\t\t\tmessage = err.Error()\n\t\t} else {\n\t\t\tcode = response.Code\n\t\t\tmessage = response.Message\n\t\t}\n\n\t\tlog.Infof(\"%-13s | %-12s | %s %s | {code: %d, message: %s}\", latency, ip, pad.Right(method, 5, \"\"), path, code, message)\n\t}\n}\n"
  },
  {
    "path": "demo17/router/middleware/requestid.go",
    "content": "package middleware\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/satori/go.uuid\"\n)\n\nfunc RequestId() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// Check for incoming header, use it if exists\n\t\trequestId := c.Request.Header.Get(\"X-Request-Id\")\n\n\t\t// Create request id with UUID4\n\t\tif requestId == \"\" {\n\t\t\tu4, _ := uuid.NewV4()\n\t\t\trequestId = u4.String()\n\t\t}\n\n\t\t// Expose it for use in the application\n\t\tc.Set(\"X-Request-Id\", requestId)\n\n\t\t// Set X-Request-Id header\n\t\tc.Writer.Header().Set(\"X-Request-Id\", requestId)\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "demo17/router/router.go",
    "content": "package router\n\nimport (\n\t\"net/http\"\n\n\t_ \"apiserver/docs\" // docs is generated by Swag CLI, you have to import it.\n\t\"apiserver/handler/sd\"\n\t\"apiserver/handler/user\"\n\t\"apiserver/router/middleware\"\n\n\t\"github.com/gin-contrib/pprof\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/swaggo/gin-swagger\"\n\t\"github.com/swaggo/gin-swagger/swaggerFiles\"\n)\n\n// Load loads the middlewares, routes, handlers.\nfunc Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {\n\t// Middlewares.\n\tg.Use(gin.Recovery())\n\tg.Use(middleware.NoCache)\n\tg.Use(middleware.Options)\n\tg.Use(middleware.Secure)\n\tg.Use(mw...)\n\t// 404 Handler.\n\tg.NoRoute(func(c *gin.Context) {\n\t\tc.String(http.StatusNotFound, \"The incorrect API route.\")\n\t})\n\n\t// swagger api docs\n\tg.GET(\"/swagger/*any\", ginSwagger.WrapHandler(swaggerFiles.Handler))\n\n\t// pprof router\n\tpprof.Register(g)\n\n\t// api for authentication functionalities\n\tg.POST(\"/login\", user.Login)\n\n\t// The user handlers, requiring authentication\n\tu := g.Group(\"/v1/user\")\n\tu.Use(middleware.AuthMiddleware())\n\t{\n\t\tu.POST(\"\", user.Create)\n\t\tu.DELETE(\"/:id\", user.Delete)\n\t\tu.PUT(\"/:id\", user.Update)\n\t\tu.GET(\"\", user.List)\n\t\tu.GET(\"/:username\", user.Get)\n\t}\n\n\t// The health check handlers\n\tsvcd := g.Group(\"/sd\")\n\t{\n\t\tsvcd.GET(\"/health\", sd.HealthCheck)\n\t\tsvcd.GET(\"/disk\", sd.DiskCheck)\n\t\tsvcd.GET(\"/cpu\", sd.CPUCheck)\n\t\tsvcd.GET(\"/ram\", sd.RAMCheck)\n\t}\n\n\treturn g\n}\n"
  },
  {
    "path": "demo17/service/service.go",
    "content": "package service\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\n\t\"apiserver/model\"\n\t\"apiserver/util\"\n)\n\nfunc ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) {\n\tinfos := make([]*model.UserInfo, 0)\n\tusers, count, err := model.ListUser(username, offset, limit)\n\tif err != nil {\n\t\treturn nil, count, err\n\t}\n\n\tids := []uint64{}\n\tfor _, user := range users {\n\t\tids = append(ids, user.Id)\n\t}\n\n\twg := sync.WaitGroup{}\n\tuserList := model.UserList{\n\t\tLock:  new(sync.Mutex),\n\t\tIdMap: make(map[uint64]*model.UserInfo, len(users)),\n\t}\n\n\terrChan := make(chan error, 1)\n\tfinished := make(chan bool, 1)\n\n\t// Improve query efficiency in parallel\n\tfor _, u := range users {\n\t\twg.Add(1)\n\t\tgo func(u *model.UserModel) {\n\t\t\tdefer wg.Done()\n\n\t\t\tshortId, err := util.GenShortId()\n\t\t\tif err != nil {\n\t\t\t\terrChan <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tuserList.Lock.Lock()\n\t\t\tdefer userList.Lock.Unlock()\n\t\t\tuserList.IdMap[u.Id] = &model.UserInfo{\n\t\t\t\tId:        u.Id,\n\t\t\t\tUsername:  u.Username,\n\t\t\t\tSayHello:  fmt.Sprintf(\"Hello %s\", shortId),\n\t\t\t\tPassword:  u.Password,\n\t\t\t\tCreatedAt: u.CreatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t\tUpdatedAt: u.UpdatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t}\n\t\t}(u)\n\t}\n\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(finished)\n\t}()\n\n\tselect {\n\tcase <-finished:\n\tcase err := <-errChan:\n\t\treturn nil, count, err\n\t}\n\n\tfor _, id := range ids {\n\t\tinfos = append(infos, userList.IdMap[id])\n\t}\n\n\treturn infos, count, nil\n}\n"
  },
  {
    "path": "demo17/util/util.go",
    "content": "package util\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/teris-io/shortid\"\n)\n\nfunc GenShortId() (string, error) {\n\treturn shortid.Generate()\n}\n\nfunc GetReqID(c *gin.Context) string {\n\tv, ok := c.Get(\"X-Request-Id\")\n\tif !ok {\n\t\treturn \"\"\n\t}\n\tif requestId, ok := v.(string); ok {\n\t\treturn requestId\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "demo17/util/util_test.go",
    "content": "package util\n\nimport (\n\t\"testing\"\n)\n\nfunc TestGenShortId(t *testing.T) {\n\tshortId, err := GenShortId()\n\tif shortId == \"\" || err != nil {\n\t\tt.Error(\"GenShortId failed!\")\n\t}\n\n\tt.Log(\"GenShortId test pass\")\n}\n\nfunc BenchmarkGenShortId(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\tGenShortId()\n\t}\n}\n\nfunc BenchmarkGenShortIdTimeConsuming(b *testing.B) {\n\tb.StopTimer() //调用该函数停止压力测试的时间计数\n\n\tshortId, err := GenShortId()\n\tif shortId == \"\" || err != nil {\n\t\tb.Error(err)\n\t}\n\n\tb.StartTimer() //重新开始时间\n\n\tfor i := 0; i < b.N; i++ {\n\t\tGenShortId()\n\t}\n}\n"
  }
]