├── .gitignore ├── README.md ├── client ├── README.md ├── package.json ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── assets │ │ └── todo.png │ ├── components │ │ ├── HelloWorld.vue │ │ └── Todo.vue │ ├── main.js │ ├── router.js │ └── views │ │ ├── About.vue │ │ ├── Health.vue │ │ └── Home.vue ├── tests │ └── unit │ │ ├── .eslintrc.js │ │ └── example.spec.js └── vue.config.js ├── server ├── config.yaml ├── config │ └── config.go ├── docs │ ├── docs.go │ ├── swagger.json │ └── swagger.yaml ├── errno │ ├── code.go │ └── errno.go ├── go.mod ├── go.sum ├── handler │ ├── handler.go │ ├── health.go │ └── todo.go ├── main.go ├── middleware │ └── middleware.go ├── model │ ├── init.go │ └── todo.go ├── router │ └── router.go └── token │ └── token.go └── todogif.gif /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | 23 | package-lock.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # todos-go 2 | A todos restful-api service base on go language, use gin+gorm+vue. 3 | 4 | This todos servie is for learning go web development, Use a front-end and back-end separation approach. 5 | front-end use Vue and back-end use Gin. just for fun, for learn. 6 | 7 | ## show 8 | ![screentshot](todogif.gif) 9 | 10 | ## how to run 11 | ``` 12 | > git clone https://github.com/shiniao/mini_todo.git 13 | 14 | # mysql create database: 15 | > create database todo; 16 | 17 | # Then change the file config.yaml for your needs. 18 | 19 | # run server 20 | > cd server 21 | > go run main.go 22 | 23 | # run client 24 | cd client 25 | npm install 26 | npm run serve 27 | ``` 28 | 29 | ## todos api 30 | 31 | You can visit `.../swagger/index.html` for more detail. 32 | 33 | First, use todos api you need to get a token, Then take tokens with each visit: 34 | 35 | ```shell script 36 | POST /token get a token, need take `key:matata` 37 | ``` 38 | 39 | That's all api: 40 | ``` 41 | GET /v1/todos 42 | GET /v1/todos/:id 43 | POST /v1/todos 44 | PUT /v1/todos/:id 45 | DELETE /v1/todos/:id 46 | ``` -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # client 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Run your tests 19 | ``` 20 | npm run test 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | npm run lint 26 | ``` 27 | 28 | ### Run your unit tests 29 | ``` 30 | npm run test:unit 31 | ``` 32 | 33 | ### Customize configuration 34 | See [Configuration Reference](https://cli.vuejs.org/config/). 35 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint", 9 | "test:unit": "vue-cli-service test:unit" 10 | }, 11 | "dependencies": { 12 | "ant-design-vue": "^1.3.17", 13 | "axios": "^0.19.0", 14 | "vue": "^2.6.10", 15 | "vue-notification": "^1.3.16", 16 | "vue-router": "^3.0.3" 17 | }, 18 | "devDependencies": { 19 | "@vue/cli-plugin-eslint": "^3.11.0", 20 | "@vue/cli-plugin-unit-mocha": "^3.11.0", 21 | "@vue/cli-service": "^3.11.0", 22 | "@vue/test-utils": "1.0.0-beta.29", 23 | "babel-eslint": "^10.0.1", 24 | "chai": "^4.1.2", 25 | "eslint": "^5.16.0", 26 | "eslint-plugin-vue": "^5.0.0", 27 | "less": "^3.0.4", 28 | "less-loader": "^5.0.0", 29 | "vue-template-compiler": "^2.6.10" 30 | }, 31 | "eslintConfig": { 32 | "root": true, 33 | "env": { 34 | "node": true 35 | }, 36 | "extends": [ 37 | "plugin:vue/essential", 38 | "eslint:recommended" 39 | ], 40 | "rules": {}, 41 | "parserOptions": { 42 | "parser": "babel-eslint" 43 | }, 44 | "overrides": [ 45 | { 46 | "files": [ 47 | "**/__tests__/*.{j,t}s?(x)" 48 | ], 49 | "env": { 50 | "mocha": true 51 | } 52 | } 53 | ] 54 | }, 55 | "postcss": { 56 | "plugins": { 57 | "autoprefixer": {} 58 | } 59 | }, 60 | "browserslist": [ 61 | "> 1%", 62 | "last 2 versions" 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ischaojie/gtodo/6f4b39ae6aea83158943b22ad2119d9205465354/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | client 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /client/src/App.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 63 | 64 | -------------------------------------------------------------------------------- /client/src/assets/todo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ischaojie/gtodo/6f4b39ae6aea83158943b22ad2119d9205465354/client/src/assets/todo.png -------------------------------------------------------------------------------- /client/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 41 | 42 | 43 | 59 | -------------------------------------------------------------------------------- /client/src/components/Todo.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 51 | 52 | -------------------------------------------------------------------------------- /client/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import Antd from 'ant-design-vue' 5 | import 'ant-design-vue/dist/antd.css' 6 | import axios from 'axios' 7 | import Notifications from 'vue-notification' 8 | 9 | Vue.config.productionTip = false 10 | 11 | Vue.use(Antd) 12 | Vue.use(Notifications) 13 | 14 | axios.post('/api/token', JSON.stringify({ 15 | key: 'matata' 16 | })).then(res => { 17 | axios.defaults.headers.common['Authorization'] = "Bearer "+res.data.data.token; 18 | console.info("token", res.data.data.token) 19 | 20 | new Vue({ 21 | router, 22 | render: function (h) { 23 | return h(App) 24 | } 25 | }).$mount('#app') 26 | }).catch((error) => { 27 | console.error(error) 28 | }); 29 | 30 | -------------------------------------------------------------------------------- /client/src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Home from './views/Home.vue' 4 | import Health from "./views/Health"; 5 | 6 | Vue.use(Router) 7 | 8 | export default new Router({ 9 | mode: 'history', 10 | base: process.env.BASE_URL, 11 | routes: [ 12 | { 13 | path: '/', 14 | name: 'home', 15 | component: Home 16 | }, 17 | { 18 | path: '/health', 19 | name: 'health', 20 | component: Health 21 | }, 22 | { 23 | path: '/about', 24 | name: 'about', 25 | // route level code-splitting 26 | // this generates a separate chunk (about.[hash].js) for this route 27 | // which is lazy-loaded when the route is visited. 28 | component: function () { 29 | return import(/* webpackChunkName: "about" */ './views/About.vue') 30 | } 31 | } 32 | ] 33 | }) 34 | -------------------------------------------------------------------------------- /client/src/views/About.vue: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /client/src/views/Health.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 51 | 52 | -------------------------------------------------------------------------------- /client/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 84 | 85 | 89 | -------------------------------------------------------------------------------- /client/tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true 4 | } 5 | } -------------------------------------------------------------------------------- /client/tests/unit/example.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { shallowMount } from '@vue/test-utils' 3 | import HelloWorld from '@/components/HelloWorld.vue' 4 | 5 | describe('HelloWorld.vue', () => { 6 | it('renders props.msg when passed', () => { 7 | const msg = 'new message' 8 | const wrapper = shallowMount(HelloWorld, { 9 | propsData: { msg } 10 | }) 11 | expect(wrapper.text()).to.include(msg) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /client/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | devServer: { 3 | proxy: { 4 | '/api': { 5 | target: 'http://localhost:8081/', //对应自己的接口 6 | changeOrigin: true, 7 | ws: true, 8 | pathRewrite: { 9 | '^/api': '' 10 | } 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /server/config.yaml: -------------------------------------------------------------------------------- 1 | runmode: debug # 开发模式, debug, release, test 2 | addr: :8081 # HTTP绑定端口 3 | name: apiserver # API Server的名字 4 | url: http://127.0.0.1:8081 # pingServer函数请求的API服务器的ip:port 5 | max_ping_count: 10 # pingServer函数try的次数 6 | key: matata 7 | jwt_secret: Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5 8 | db: 9 | dbname: todo 10 | addr: 127.0.0.1:3306 11 | username: root 12 | password: zhezhezhu -------------------------------------------------------------------------------- /server/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/fsnotify/fsnotify" 5 | "github.com/spf13/viper" 6 | "log" 7 | "strings" 8 | ) 9 | 10 | type Config struct { 11 | Name string 12 | } 13 | 14 | // * 初始化配置文件 15 | func Init(cfg string) error { 16 | c := Config{Name:cfg} 17 | if err := c.initConfig(); err != nil { 18 | return err 19 | } 20 | // * 热更新 21 | c.WatchConfig() 22 | return nil 23 | } 24 | 25 | func (c *Config) initConfig() error { 26 | // * 指定配置文件 27 | if c.Name != "" { 28 | // * 设置配置文件名 29 | viper.SetConfigFile(c.Name) 30 | }else { 31 | // * 未指定配置文件,读取默认路径 32 | viper.AddConfigPath(".") 33 | viper.SetConfigName("config") 34 | } 35 | // * 设置config文件类型 36 | viper.SetConfigType("yaml") 37 | 38 | // * 读取环境变量 39 | viper.AutomaticEnv() 40 | // * 设置环境变量前缀 41 | viper.SetEnvPrefix("api") 42 | replacer := strings.NewReplacer(",", "_") 43 | viper.SetEnvKeyReplacer(replacer) 44 | 45 | // * 读取配置文件内容,使用viper.get获取配置 46 | if err:=viper.ReadInConfig(); err != nil { 47 | return err 48 | } 49 | return nil 50 | } 51 | 52 | // * 监控配置文件变化并热加载 53 | func (c *Config) WatchConfig() { 54 | viper.WatchConfig() 55 | viper.OnConfigChange(func(e fsnotify.Event) { 56 | log.Printf("Config file changed: %s", e.Name) 57 | }) 58 | } 59 | 60 | 61 | -------------------------------------------------------------------------------- /server/docs/docs.go: -------------------------------------------------------------------------------- 1 | // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT 2 | // This file was generated by swaggo/swag at 3 | // 2019-10-15 10:46:00.642260008 +0800 CST m=+0.029875648 4 | 5 | package docs 6 | 7 | import ( 8 | "bytes" 9 | "encoding/json" 10 | "strings" 11 | 12 | "github.com/alecthomas/template" 13 | "github.com/swaggo/swag" 14 | ) 15 | 16 | var doc = `{ 17 | "schemes": {{ marshal .Schemes }}, 18 | "swagger": "2.0", 19 | "info": { 20 | "description": "{{.Description}}", 21 | "title": "{{.Title}}", 22 | "termsOfService": "http://me.shiniao.fun/", 23 | "contact": { 24 | "name": "shiniao", 25 | "url": "http://me.shiniao.fun/", 26 | "email": "zhuzhezhe5@gmail.com" 27 | }, 28 | "license": { 29 | "name": "Apache 2.0", 30 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 31 | }, 32 | "version": "{{.Version}}" 33 | }, 34 | "host": "{{.Host}}", 35 | "basePath": "{{.BasePath}}", 36 | "paths": { 37 | "/sd/cpu": { 38 | "get": { 39 | "description": "Checks the cpu usage", 40 | "consumes": [ 41 | "application/json" 42 | ], 43 | "produces": [ 44 | "application/json" 45 | ], 46 | "tags": [ 47 | "sd" 48 | ], 49 | "summary": "Checks the cpu usage", 50 | "responses": { 51 | "200": { 52 | "description": "{\"status\":\"OK\", \"info\":\"Load average: 1.82, 1.05, 0.85 | Cores: 2\"}", 53 | "schema": { 54 | "$ref": "#/definitions/handler.message" 55 | } 56 | } 57 | } 58 | } 59 | }, 60 | "/sd/disk": { 61 | "get": { 62 | "description": "Checks the disk usage", 63 | "consumes": [ 64 | "application/json" 65 | ], 66 | "produces": [ 67 | "application/json" 68 | ], 69 | "tags": [ 70 | "sd" 71 | ], 72 | "summary": "Checks the disk usage", 73 | "responses": { 74 | "200": { 75 | "description": "{\"status\":\"OK\", \"info\":\"Free space: 44429MB (43GB) / 119674MB (116GB) | Used: 39%\"}", 76 | "schema": { 77 | "$ref": "#/definitions/handler.message" 78 | } 79 | } 80 | } 81 | } 82 | }, 83 | "/sd/health": { 84 | "get": { 85 | "description": "Shows OK as the ping-pong result", 86 | "consumes": [ 87 | "application/json" 88 | ], 89 | "produces": [ 90 | "application/json" 91 | ], 92 | "tags": [ 93 | "sd" 94 | ], 95 | "summary": "Shows OK as the ping-pong result", 96 | "responses": { 97 | "200": { 98 | "description": "{\"status\":\"OK\", \"info\":\"\"}", 99 | "schema": { 100 | "$ref": "#/definitions/handler.message" 101 | } 102 | } 103 | } 104 | } 105 | }, 106 | "/sd/ram": { 107 | "get": { 108 | "description": "Checks the ram usage", 109 | "consumes": [ 110 | "application/json" 111 | ], 112 | "produces": [ 113 | "application/json" 114 | ], 115 | "tags": [ 116 | "sd" 117 | ], 118 | "summary": "Checks the ram usage", 119 | "responses": { 120 | "200": { 121 | "description": "{\"status\":\"OK\", \"info\":\"Free space: 5293MB (5GB) / 7665MB (7GB) | Used: 69%\"}", 122 | "schema": { 123 | "$ref": "#/definitions/handler.message" 124 | } 125 | } 126 | } 127 | } 128 | }, 129 | "/v1/todos": { 130 | "get": { 131 | "description": "Get all todos from database", 132 | "consumes": [ 133 | "application/json" 134 | ], 135 | "produces": [ 136 | "application/json" 137 | ], 138 | "tags": [ 139 | "todo" 140 | ], 141 | "summary": "Get all todos", 142 | "responses": { 143 | "200": { 144 | "description": "{\"code\":0,\"message\":\"OK\",\"data\":{\"total\":233, \"todos\":[{\"ID\":91,\"title\": \"烫头\", \"completed\": 1,\"CreatedAt\": \"2019-10-12T10:10:05+08:00\",\"UpdatedAt\": \"2019-10-12T10:16:24+08:00\",\"DeletedAt\": null}]}}", 145 | "schema": { 146 | "$ref": "#/definitions/model.TodoModel" 147 | } 148 | } 149 | } 150 | } 151 | }, 152 | "/v1/todos/": { 153 | "post": { 154 | "description": "Add a new todo", 155 | "consumes": [ 156 | "application/json" 157 | ], 158 | "produces": [ 159 | "application/json" 160 | ], 161 | "tags": [ 162 | "todo" 163 | ], 164 | "summary": "Add new todos to the database", 165 | "parameters": [ 166 | { 167 | "description": "The todo info", 168 | "name": "todo", 169 | "in": "body", 170 | "required": true, 171 | "schema": { 172 | "type": "object", 173 | "$ref": "#/definitions/model.TodoModel" 174 | } 175 | } 176 | ], 177 | "responses": { 178 | "200": { 179 | "description": "{\"code\":0,\"message\":\"OK\",\"data\":\"create successful.\"}", 180 | "schema": { 181 | "$ref": "#/definitions/handler.Response" 182 | } 183 | } 184 | } 185 | } 186 | }, 187 | "/v1/todos/{id}": { 188 | "get": { 189 | "description": "Get a todo", 190 | "consumes": [ 191 | "application/json" 192 | ], 193 | "produces": [ 194 | "application/json" 195 | ], 196 | "tags": [ 197 | "todo" 198 | ], 199 | "summary": "Get a todo", 200 | "responses": { 201 | "200": { 202 | "description": "{\"code\":0,\"message\":\"OK\",\"data\":{\"ID\":91,\"title\": \"烫头\", \"completed\": 1,\"CreatedAt\": \"2019-10-12T10:10:05+08:00\",\"UpdatedAt\": \"2019-10-12T10:16:24+08:00\",\"DeletedAt\": null}}", 203 | "schema": { 204 | "$ref": "#/definitions/model.TodoModel" 205 | } 206 | } 207 | } 208 | }, 209 | "put": { 210 | "description": "Update a todo", 211 | "consumes": [ 212 | "application/json" 213 | ], 214 | "produces": [ 215 | "application/json" 216 | ], 217 | "tags": [ 218 | "todo" 219 | ], 220 | "summary": "Update a todo", 221 | "parameters": [ 222 | { 223 | "type": "integer", 224 | "description": "The todo's database id index num", 225 | "name": "id", 226 | "in": "path", 227 | "required": true 228 | }, 229 | { 230 | "description": "The todo info", 231 | "name": "todo", 232 | "in": "body", 233 | "required": true, 234 | "schema": { 235 | "type": "object", 236 | "$ref": "#/definitions/model.TodoModel" 237 | } 238 | } 239 | ], 240 | "responses": { 241 | "200": { 242 | "description": "{\"code\":0,\"message\":\"OK\",\"data\":\"update successful.\"}", 243 | "schema": { 244 | "$ref": "#/definitions/handler.Response" 245 | } 246 | } 247 | } 248 | }, 249 | "delete": { 250 | "description": "Delete a todo", 251 | "consumes": [ 252 | "application/json" 253 | ], 254 | "produces": [ 255 | "application/json" 256 | ], 257 | "tags": [ 258 | "todo" 259 | ], 260 | "summary": "Delete a todo", 261 | "responses": { 262 | "200": { 263 | "description": "{\"code\":0,\"message\":\"OK\",\"data\":\"delete successful.\"}", 264 | "schema": { 265 | "$ref": "#/definitions/handler.Response" 266 | } 267 | } 268 | } 269 | } 270 | } 271 | }, 272 | "definitions": { 273 | "handler.Response": { 274 | "type": "object", 275 | "properties": { 276 | "code": { 277 | "type": "integer" 278 | }, 279 | "data": { 280 | "type": "object" 281 | }, 282 | "message": { 283 | "type": "string" 284 | } 285 | } 286 | }, 287 | "handler.message": { 288 | "type": "object", 289 | "properties": { 290 | "info": { 291 | "type": "string" 292 | }, 293 | "status": { 294 | "type": "string" 295 | } 296 | } 297 | }, 298 | "model.TodoModel": { 299 | "type": "object", 300 | "properties": { 301 | "completed": { 302 | "type": "integer" 303 | }, 304 | "title": { 305 | "type": "string" 306 | } 307 | } 308 | } 309 | } 310 | }` 311 | 312 | type swaggerInfo struct { 313 | Version string 314 | Host string 315 | BasePath string 316 | Schemes []string 317 | Title string 318 | Description string 319 | } 320 | 321 | // SwaggerInfo holds exported Swagger Info so clients can modify it 322 | var SwaggerInfo = swaggerInfo{ 323 | Version: "1.0", 324 | Host: "todo.shiniao.fun", 325 | BasePath: "/v1", 326 | Schemes: []string{}, 327 | Title: "A todos application API", 328 | Description: "This is a todos application server.", 329 | } 330 | 331 | type s struct{} 332 | 333 | func (s *s) ReadDoc() string { 334 | sInfo := SwaggerInfo 335 | sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) 336 | 337 | t, err := template.New("swagger_info").Funcs(template.FuncMap{ 338 | "marshal": func(v interface{}) string { 339 | a, _ := json.Marshal(v) 340 | return string(a) 341 | }, 342 | }).Parse(doc) 343 | if err != nil { 344 | return doc 345 | } 346 | 347 | var tpl bytes.Buffer 348 | if err := t.Execute(&tpl, sInfo); err != nil { 349 | return doc 350 | } 351 | 352 | return tpl.String() 353 | } 354 | 355 | func init() { 356 | swag.Register(swag.Name, &s{}) 357 | } 358 | -------------------------------------------------------------------------------- /server/docs/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "description": "This is a todos application server.", 5 | "title": "A todos application API", 6 | "termsOfService": "http://me.shiniao.fun/", 7 | "contact": { 8 | "name": "shiniao", 9 | "url": "http://me.shiniao.fun/", 10 | "email": "zhuzhezhe5@gmail.com" 11 | }, 12 | "license": { 13 | "name": "Apache 2.0", 14 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 15 | }, 16 | "version": "1.0" 17 | }, 18 | "host": "todo.shiniao.fun", 19 | "basePath": "/v1", 20 | "paths": { 21 | "/sd/cpu": { 22 | "get": { 23 | "description": "Checks the cpu usage", 24 | "consumes": [ 25 | "application/json" 26 | ], 27 | "produces": [ 28 | "application/json" 29 | ], 30 | "tags": [ 31 | "sd" 32 | ], 33 | "summary": "Checks the cpu usage", 34 | "responses": { 35 | "200": { 36 | "description": "{\"status\":\"OK\", \"info\":\"Load average: 1.82, 1.05, 0.85 | Cores: 2\"}", 37 | "schema": { 38 | "$ref": "#/definitions/handler.message" 39 | } 40 | } 41 | } 42 | } 43 | }, 44 | "/sd/disk": { 45 | "get": { 46 | "description": "Checks the disk usage", 47 | "consumes": [ 48 | "application/json" 49 | ], 50 | "produces": [ 51 | "application/json" 52 | ], 53 | "tags": [ 54 | "sd" 55 | ], 56 | "summary": "Checks the disk usage", 57 | "responses": { 58 | "200": { 59 | "description": "{\"status\":\"OK\", \"info\":\"Free space: 44429MB (43GB) / 119674MB (116GB) | Used: 39%\"}", 60 | "schema": { 61 | "$ref": "#/definitions/handler.message" 62 | } 63 | } 64 | } 65 | } 66 | }, 67 | "/sd/health": { 68 | "get": { 69 | "description": "Shows OK as the ping-pong result", 70 | "consumes": [ 71 | "application/json" 72 | ], 73 | "produces": [ 74 | "application/json" 75 | ], 76 | "tags": [ 77 | "sd" 78 | ], 79 | "summary": "Shows OK as the ping-pong result", 80 | "responses": { 81 | "200": { 82 | "description": "{\"status\":\"OK\", \"info\":\"\"}", 83 | "schema": { 84 | "$ref": "#/definitions/handler.message" 85 | } 86 | } 87 | } 88 | } 89 | }, 90 | "/sd/ram": { 91 | "get": { 92 | "description": "Checks the ram usage", 93 | "consumes": [ 94 | "application/json" 95 | ], 96 | "produces": [ 97 | "application/json" 98 | ], 99 | "tags": [ 100 | "sd" 101 | ], 102 | "summary": "Checks the ram usage", 103 | "responses": { 104 | "200": { 105 | "description": "{\"status\":\"OK\", \"info\":\"Free space: 5293MB (5GB) / 7665MB (7GB) | Used: 69%\"}", 106 | "schema": { 107 | "$ref": "#/definitions/handler.message" 108 | } 109 | } 110 | } 111 | } 112 | }, 113 | "/v1/todos": { 114 | "get": { 115 | "description": "Get all todos from database", 116 | "consumes": [ 117 | "application/json" 118 | ], 119 | "produces": [ 120 | "application/json" 121 | ], 122 | "tags": [ 123 | "todo" 124 | ], 125 | "summary": "Get all todos", 126 | "responses": { 127 | "200": { 128 | "description": "{\"code\":0,\"message\":\"OK\",\"data\":{\"total\":233, \"todos\":[{\"ID\":91,\"title\": \"烫头\", \"completed\": 1,\"CreatedAt\": \"2019-10-12T10:10:05+08:00\",\"UpdatedAt\": \"2019-10-12T10:16:24+08:00\",\"DeletedAt\": null}]}}", 129 | "schema": { 130 | "$ref": "#/definitions/model.TodoModel" 131 | } 132 | } 133 | } 134 | } 135 | }, 136 | "/v1/todos/": { 137 | "post": { 138 | "description": "Add a new todo", 139 | "consumes": [ 140 | "application/json" 141 | ], 142 | "produces": [ 143 | "application/json" 144 | ], 145 | "tags": [ 146 | "todo" 147 | ], 148 | "summary": "Add new todos to the database", 149 | "parameters": [ 150 | { 151 | "description": "The todo info", 152 | "name": "todo", 153 | "in": "body", 154 | "required": true, 155 | "schema": { 156 | "type": "object", 157 | "$ref": "#/definitions/model.TodoModel" 158 | } 159 | } 160 | ], 161 | "responses": { 162 | "200": { 163 | "description": "{\"code\":0,\"message\":\"OK\",\"data\":\"create successful.\"}", 164 | "schema": { 165 | "$ref": "#/definitions/handler.Response" 166 | } 167 | } 168 | } 169 | } 170 | }, 171 | "/v1/todos/{id}": { 172 | "get": { 173 | "description": "Get a todo", 174 | "consumes": [ 175 | "application/json" 176 | ], 177 | "produces": [ 178 | "application/json" 179 | ], 180 | "tags": [ 181 | "todo" 182 | ], 183 | "summary": "Get a todo", 184 | "responses": { 185 | "200": { 186 | "description": "{\"code\":0,\"message\":\"OK\",\"data\":{\"ID\":91,\"title\": \"烫头\", \"completed\": 1,\"CreatedAt\": \"2019-10-12T10:10:05+08:00\",\"UpdatedAt\": \"2019-10-12T10:16:24+08:00\",\"DeletedAt\": null}}", 187 | "schema": { 188 | "$ref": "#/definitions/model.TodoModel" 189 | } 190 | } 191 | } 192 | }, 193 | "put": { 194 | "description": "Update a todo", 195 | "consumes": [ 196 | "application/json" 197 | ], 198 | "produces": [ 199 | "application/json" 200 | ], 201 | "tags": [ 202 | "todo" 203 | ], 204 | "summary": "Update a todo", 205 | "parameters": [ 206 | { 207 | "type": "integer", 208 | "description": "The todo's database id index num", 209 | "name": "id", 210 | "in": "path", 211 | "required": true 212 | }, 213 | { 214 | "description": "The todo info", 215 | "name": "todo", 216 | "in": "body", 217 | "required": true, 218 | "schema": { 219 | "type": "object", 220 | "$ref": "#/definitions/model.TodoModel" 221 | } 222 | } 223 | ], 224 | "responses": { 225 | "200": { 226 | "description": "{\"code\":0,\"message\":\"OK\",\"data\":\"update successful.\"}", 227 | "schema": { 228 | "$ref": "#/definitions/handler.Response" 229 | } 230 | } 231 | } 232 | }, 233 | "delete": { 234 | "description": "Delete a todo", 235 | "consumes": [ 236 | "application/json" 237 | ], 238 | "produces": [ 239 | "application/json" 240 | ], 241 | "tags": [ 242 | "todo" 243 | ], 244 | "summary": "Delete a todo", 245 | "responses": { 246 | "200": { 247 | "description": "{\"code\":0,\"message\":\"OK\",\"data\":\"delete successful.\"}", 248 | "schema": { 249 | "$ref": "#/definitions/handler.Response" 250 | } 251 | } 252 | } 253 | } 254 | } 255 | }, 256 | "definitions": { 257 | "handler.Response": { 258 | "type": "object", 259 | "properties": { 260 | "code": { 261 | "type": "integer" 262 | }, 263 | "data": { 264 | "type": "object" 265 | }, 266 | "message": { 267 | "type": "string" 268 | } 269 | } 270 | }, 271 | "handler.message": { 272 | "type": "object", 273 | "properties": { 274 | "info": { 275 | "type": "string" 276 | }, 277 | "status": { 278 | "type": "string" 279 | } 280 | } 281 | }, 282 | "model.TodoModel": { 283 | "type": "object", 284 | "properties": { 285 | "completed": { 286 | "type": "integer" 287 | }, 288 | "title": { 289 | "type": "string" 290 | } 291 | } 292 | } 293 | } 294 | } -------------------------------------------------------------------------------- /server/docs/swagger.yaml: -------------------------------------------------------------------------------- 1 | basePath: /v1 2 | definitions: 3 | handler.Response: 4 | properties: 5 | code: 6 | type: integer 7 | data: 8 | type: object 9 | message: 10 | type: string 11 | type: object 12 | handler.message: 13 | properties: 14 | info: 15 | type: string 16 | status: 17 | type: string 18 | type: object 19 | model.TodoModel: 20 | properties: 21 | completed: 22 | type: integer 23 | title: 24 | type: string 25 | type: object 26 | host: todo.shiniao.fun 27 | info: 28 | contact: 29 | email: zhuzhezhe5@gmail.com 30 | name: shiniao 31 | url: http://me.shiniao.fun/ 32 | description: This is a todos application server. 33 | license: 34 | name: Apache 2.0 35 | url: http://www.apache.org/licenses/LICENSE-2.0.html 36 | termsOfService: http://me.shiniao.fun/ 37 | title: A todos application API 38 | version: "1.0" 39 | paths: 40 | /sd/cpu: 41 | get: 42 | consumes: 43 | - application/json 44 | description: Checks the cpu usage 45 | produces: 46 | - application/json 47 | responses: 48 | "200": 49 | description: '{"status":"OK", "info":"Load average: 1.82, 1.05, 0.85 | Cores: 50 | 2"}' 51 | schema: 52 | $ref: '#/definitions/handler.message' 53 | summary: Checks the cpu usage 54 | tags: 55 | - sd 56 | /sd/disk: 57 | get: 58 | consumes: 59 | - application/json 60 | description: Checks the disk usage 61 | produces: 62 | - application/json 63 | responses: 64 | "200": 65 | description: '{"status":"OK", "info":"Free space: 44429MB (43GB) / 119674MB 66 | (116GB) | Used: 39%"}' 67 | schema: 68 | $ref: '#/definitions/handler.message' 69 | summary: Checks the disk usage 70 | tags: 71 | - sd 72 | /sd/health: 73 | get: 74 | consumes: 75 | - application/json 76 | description: Shows OK as the ping-pong result 77 | produces: 78 | - application/json 79 | responses: 80 | "200": 81 | description: '{"status":"OK", "info":""}' 82 | schema: 83 | $ref: '#/definitions/handler.message' 84 | summary: Shows OK as the ping-pong result 85 | tags: 86 | - sd 87 | /sd/ram: 88 | get: 89 | consumes: 90 | - application/json 91 | description: Checks the ram usage 92 | produces: 93 | - application/json 94 | responses: 95 | "200": 96 | description: '{"status":"OK", "info":"Free space: 5293MB (5GB) / 7665MB 97 | (7GB) | Used: 69%"}' 98 | schema: 99 | $ref: '#/definitions/handler.message' 100 | summary: Checks the ram usage 101 | tags: 102 | - sd 103 | /v1/todos: 104 | get: 105 | consumes: 106 | - application/json 107 | description: Get all todos from database 108 | produces: 109 | - application/json 110 | responses: 111 | "200": 112 | description: '{"code":0,"message":"OK","data":{"total":233, "todos":[{"ID":91,"title": 113 | "烫头", "completed": 1,"CreatedAt": "2019-10-12T10:10:05+08:00","UpdatedAt": 114 | "2019-10-12T10:16:24+08:00","DeletedAt": null}]}}' 115 | schema: 116 | $ref: '#/definitions/model.TodoModel' 117 | summary: Get all todos 118 | tags: 119 | - todo 120 | /v1/todos/: 121 | post: 122 | consumes: 123 | - application/json 124 | description: Add a new todo 125 | parameters: 126 | - description: The todo info 127 | in: body 128 | name: todo 129 | required: true 130 | schema: 131 | $ref: '#/definitions/model.TodoModel' 132 | type: object 133 | produces: 134 | - application/json 135 | responses: 136 | "200": 137 | description: '{"code":0,"message":"OK","data":"create successful."}' 138 | schema: 139 | $ref: '#/definitions/handler.Response' 140 | summary: Add new todos to the database 141 | tags: 142 | - todo 143 | /v1/todos/{id}: 144 | delete: 145 | consumes: 146 | - application/json 147 | description: Delete a todo 148 | produces: 149 | - application/json 150 | responses: 151 | "200": 152 | description: '{"code":0,"message":"OK","data":"delete successful."}' 153 | schema: 154 | $ref: '#/definitions/handler.Response' 155 | summary: Delete a todo 156 | tags: 157 | - todo 158 | get: 159 | consumes: 160 | - application/json 161 | description: Get a todo 162 | produces: 163 | - application/json 164 | responses: 165 | "200": 166 | description: '{"code":0,"message":"OK","data":{"ID":91,"title": "烫头", "completed": 167 | 1,"CreatedAt": "2019-10-12T10:10:05+08:00","UpdatedAt": "2019-10-12T10:16:24+08:00","DeletedAt": 168 | null}}' 169 | schema: 170 | $ref: '#/definitions/model.TodoModel' 171 | summary: Get a todo 172 | tags: 173 | - todo 174 | put: 175 | consumes: 176 | - application/json 177 | description: Update a todo 178 | parameters: 179 | - description: The todo's database id index num 180 | in: path 181 | name: id 182 | required: true 183 | type: integer 184 | - description: The todo info 185 | in: body 186 | name: todo 187 | required: true 188 | schema: 189 | $ref: '#/definitions/model.TodoModel' 190 | type: object 191 | produces: 192 | - application/json 193 | responses: 194 | "200": 195 | description: '{"code":0,"message":"OK","data":"update successful."}' 196 | schema: 197 | $ref: '#/definitions/handler.Response' 198 | summary: Update a todo 199 | tags: 200 | - todo 201 | swagger: "2.0" 202 | -------------------------------------------------------------------------------- /server/errno/code.go: -------------------------------------------------------------------------------- 1 | /*定义错误代码*/ 2 | 3 | package errno 4 | 5 | 6 | var ( 7 | // Common errors 8 | OK = &Errno{Code: 0, Message: "OK"} 9 | InternalServerError = &Errno{Code: 10001, Message: "Internal server error."} 10 | ErrBind = &Errno{Code: 10002, Message: "Error occurred while binding the request body to the struct."} 11 | 12 | // user errors 13 | ErrUserNotFound = &Errno{Code: 20102, Message: "The user was not found."} 14 | 15 | ErrValidation = &Errno{Code: 20001, Message: "Validation failed."} 16 | ErrDatabase = &Errno{Code: 20002, Message: "Database error."} 17 | ErrToken = &Errno{Code: 20003, Message: "Error occurred while signing the JSON web token."} 18 | 19 | // user errors 20 | ErrEncrypt = &Errno{Code: 20101, Message: "Error occurred while encrypting the user password."} 21 | ErrTokenInvalid = &Errno{Code: 20103, Message: "The token was invalid."} 22 | ErrKeyIncorrect = &Errno{Code: 20104, Message: "The key was incorrect"} 23 | ) -------------------------------------------------------------------------------- /server/errno/errno.go: -------------------------------------------------------------------------------- 1 | package errno 2 | 3 | import "fmt" 4 | 5 | // * 自定义错误代码(code+message) 6 | // * 这是展示给用户的 7 | type Errno struct { 8 | Code int 9 | Message string 10 | } 11 | 12 | // * 实现Error接口,自定义错误类型 13 | /* 14 | type error interface { 15 | Error() string 16 | } 17 | */ 18 | 19 | func (err Errno) Error() string { 20 | return err.Message 21 | } 22 | 23 | // * 定义错误 24 | // * 这是展示给后台的 25 | type Err struct { 26 | Code int 27 | Message string 28 | Err error 29 | } 30 | 31 | func (err *Err) Error() string { 32 | return fmt.Sprintf("Err - code: %d, message: %s, error: %s", err.Code, err.Message, err.Err) 33 | } 34 | 35 | // * 新建一个自定义错误(传入code+message, err) 36 | func New(errno *Errno, err error) *Err { 37 | return &Err{ 38 | Code: errno.Code, 39 | Message: errno.Message, 40 | Err: err, 41 | } 42 | } 43 | 44 | // * 解析定制的错误 45 | func DecodeErr(err error) (int, string) { 46 | // * 没有错误返回ok 47 | if err == nil { 48 | return OK.Code, OK.Message 49 | } 50 | 51 | switch typed := err.(type) { 52 | case *Err: 53 | return typed.Code, typed.Message 54 | case *Errno: 55 | return typed.Code, typed.Message 56 | default: 57 | } 58 | 59 | return InternalServerError.Code, err.Error() 60 | } -------------------------------------------------------------------------------- /server/go.mod: -------------------------------------------------------------------------------- 1 | module mini_todo 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect 7 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 8 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 9 | github.com/fsnotify/fsnotify v1.4.7 10 | github.com/gin-gonic/gin v1.4.0 11 | github.com/go-ole/go-ole v1.2.4 // indirect 12 | github.com/jinzhu/gorm v1.9.11 13 | github.com/shirou/gopsutil v2.19.9+incompatible 14 | github.com/spf13/pflag v1.0.5 15 | github.com/spf13/viper v1.4.0 16 | github.com/swaggo/gin-swagger v1.2.0 17 | github.com/swaggo/swag v1.6.3 18 | ) 19 | -------------------------------------------------------------------------------- /server/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU= 4 | cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= 5 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 6 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 7 | github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= 8 | github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= 9 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 10 | github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4= 11 | github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 12 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= 13 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 14 | github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= 15 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= 16 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= 17 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= 18 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= 19 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 20 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= 21 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 22 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 23 | github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= 24 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 25 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 26 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 27 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 28 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 29 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 30 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 31 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 32 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 33 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 34 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 35 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 36 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 37 | github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA= 38 | github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= 39 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 40 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 41 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 42 | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= 43 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= 44 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= 45 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= 46 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= 47 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 48 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 49 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 50 | github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc= 51 | github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= 52 | github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= 53 | github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g= 54 | github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= 55 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 56 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 57 | github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= 58 | github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ= 59 | github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= 60 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 61 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 62 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 63 | github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= 64 | github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= 65 | github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0= 66 | github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= 67 | github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= 68 | github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk= 69 | github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= 70 | github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4= 71 | github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= 72 | github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880= 73 | github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= 74 | github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= 75 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 76 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 77 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 78 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 79 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 80 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 81 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 82 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 83 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 84 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 85 | github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= 86 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 87 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 88 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 89 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 90 | github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= 91 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 92 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 93 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 94 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 95 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 96 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 97 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 98 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 99 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 100 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 101 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 102 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 103 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 104 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 105 | github.com/jinzhu/gorm v1.9.11 h1:gaHGvE+UnWGlbWG4Y3FUwY1EcZ5n6S9WtqBA/uySMLE= 106 | github.com/jinzhu/gorm v1.9.11/go.mod h1:bu/pK8szGZ2puuErfU0RwyeNdsf3e6nCX/noXaVxkfw= 107 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 108 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 109 | github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= 110 | github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 111 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 112 | github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 113 | github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= 114 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 115 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 116 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 117 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 118 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 119 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 120 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 121 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 122 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 123 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 124 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 125 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 126 | github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= 127 | github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 128 | github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= 129 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 130 | github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= 131 | github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 132 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 133 | github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= 134 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 135 | github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= 136 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 137 | github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= 138 | github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 139 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 140 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 141 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 142 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 143 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 144 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 145 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 146 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 147 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 148 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 149 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 150 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 151 | github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= 152 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= 153 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 154 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 155 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 156 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 157 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 158 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 159 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 160 | github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= 161 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 162 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 163 | github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 164 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 165 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 166 | github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 167 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 168 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 169 | github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 170 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 171 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 172 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 173 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 174 | github.com/shirou/gopsutil v2.19.9+incompatible h1:IrPVlK4nfwW10DF7pW+7YJKws9NkgNzWozwwWv9FsgY= 175 | github.com/shirou/gopsutil v2.19.9+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 176 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 177 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 178 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 179 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= 180 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 181 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= 182 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 183 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= 184 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 185 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 186 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 187 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 188 | github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= 189 | github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= 190 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 191 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 192 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 193 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 194 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 195 | github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= 196 | github.com/swaggo/gin-swagger v1.2.0 h1:YskZXEiv51fjOMTsXrOetAjrMDfFaXD79PEoQBOe2W0= 197 | github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI= 198 | github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y= 199 | github.com/swaggo/swag v1.6.3 h1:N+uVPGP4H2hXoss2pt5dctoSUPKKRInr6qcTMOm0usI= 200 | github.com/swaggo/swag v1.6.3/go.mod h1:wcc83tB4Mb2aNiL/HP4MFeQdpHUrca+Rp/DRNgWAUio= 201 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 202 | github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= 203 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 204 | github.com/ugorji/go v1.1.5-pre h1:jyJKFOSEbdOc2HODrf2qcCkYOdq7zzXqA9bhW5oV4fM= 205 | github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0= 206 | github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 207 | github.com/ugorji/go/codec v1.1.5-pre h1:5YV9PsFAN+ndcCtTM7s60no7nY7eTG3LPtxhSwuxzCs= 208 | github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI= 209 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 210 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 211 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 212 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 213 | go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= 214 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 215 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 216 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 217 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 218 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 219 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= 220 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 221 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 222 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 223 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 224 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 225 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 226 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 227 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 228 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 229 | golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 230 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 231 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 232 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 233 | golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 234 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 235 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 236 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 237 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= 238 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 239 | golang.org/x/net v0.0.0-20190611141213-3f473d35a33a h1:+KkCgOMgnKSgenxTBoiwkMqTiouMIy/3o8RLdmSbGoY= 240 | golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 241 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 242 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 243 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 244 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 245 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 246 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 247 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 248 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 249 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 250 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 251 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 252 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 253 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 254 | golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 255 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 256 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= 257 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 258 | golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae h1:xiXzMMEQdQcric9hXtr1QU98MHunKK7OTtsoU6bYWs4= 259 | golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 260 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 261 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To= 262 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 263 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 264 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 265 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 266 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 267 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 268 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 269 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 270 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 271 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 272 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 273 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 274 | golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 275 | golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b h1:/mJ+GKieZA6hFDQGdWZrjj4AXPl5ylY+5HusG80roy0= 276 | golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 277 | google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= 278 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 279 | google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= 280 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 281 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 282 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 283 | google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 284 | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= 285 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 286 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 287 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 288 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 289 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 290 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 291 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 292 | gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= 293 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= 294 | gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= 295 | gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= 296 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 297 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 298 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 299 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 300 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 301 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 302 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 303 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 304 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 305 | -------------------------------------------------------------------------------- /server/handler/handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/spf13/viper" 6 | "mini_todo/errno" 7 | "mini_todo/token" 8 | "net/http" 9 | ) 10 | 11 | /*统一api返回内容*/ 12 | 13 | type Response struct { 14 | Code int `json:"code"` 15 | Message string `json:"message"` 16 | Data interface{} `json:"data"` 17 | } 18 | 19 | // * 统一返回给client的内容 20 | func SendResponse(c *gin.Context, err error, data interface{}) { 21 | code, message := errno.DecodeErr(err) 22 | 23 | c.JSON(http.StatusOK, Response{ 24 | Code: code, 25 | Message: message, 26 | Data: data, 27 | }) 28 | } 29 | 30 | type Key struct { 31 | Key string `json:"key"` 32 | } 33 | 34 | func Token(c *gin.Context) { 35 | var key Key 36 | 37 | c.BindJSON(&key) 38 | //key := c.PostForm("key") 39 | // * 判断key是否正确 40 | if key.Key != viper.GetString("key") { 41 | SendResponse(c, errno.ErrKeyIncorrect, nil) 42 | return 43 | } 44 | // * Sign the json web token. 45 | t, err := token.Sign(c,key.Key, "") 46 | if err != nil { 47 | SendResponse(c, errno.ErrToken, nil) 48 | return 49 | } 50 | 51 | SendResponse(c, nil, token.Token{Token: t}) 52 | } 53 | 54 | -------------------------------------------------------------------------------- /server/handler/health.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "github.com/shirou/gopsutil/cpu" 7 | "github.com/shirou/gopsutil/disk" 8 | "github.com/shirou/gopsutil/load" 9 | "github.com/shirou/gopsutil/mem" 10 | "net/http" 11 | ) 12 | 13 | const ( 14 | B = 1 15 | KB = 1024 * B 16 | MB = 1024 * KB 17 | GB = 1024 * MB 18 | ) 19 | 20 | type message struct { 21 | Status string `json:"status"` 22 | Info string `json:"info, omitempty"` 23 | } 24 | 25 | // @Summary Shows OK as the ping-pong result 26 | // @Description Shows OK as the ping-pong result 27 | // @Tags sd 28 | // @Accept json 29 | // @Produce json 30 | // @Success 200 {object} message "{"status":"OK", "info":""}" 31 | // @Router /sd/health [get] 32 | func HealthCheck(c *gin.Context) { 33 | mess := "ok" 34 | c.JSON(http.StatusOK, message{ 35 | Status: mess, 36 | }) 37 | } 38 | 39 | // * 检测硬盘使用量 40 | // @Summary Checks the disk usage 41 | // @Description Checks the disk usage 42 | // @Tags sd 43 | // @Accept json 44 | // @Produce json 45 | // @Success 200 {object} message "{"status":"OK", "info":"Free space: 44429MB (43GB) / 119674MB (116GB) | Used: 39%"}" 46 | // @Router /sd/disk [get] 47 | func DiskCheck(c *gin.Context) { 48 | u, _ := disk.Usage("/") 49 | 50 | usedMB := int(u.Used) / MB 51 | usedGB := int(u.Used) / GB 52 | totalMB := int(u.Total) / MB 53 | totalGB := int(u.Total) / GB 54 | usedPercent := int(u.UsedPercent) 55 | 56 | status := http.StatusOK 57 | text := "OK" 58 | 59 | if usedPercent >= 95 { 60 | status = http.StatusOK 61 | text = "CRITICAL" 62 | } else if usedPercent >= 90 { 63 | status = http.StatusTooManyRequests 64 | text = "WARNING" 65 | } 66 | 67 | mess := fmt.Sprintf("Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", usedMB, usedGB, totalMB, totalGB, usedPercent) 68 | c.JSON(status, message{ 69 | Status: text, 70 | Info: mess, 71 | }) 72 | 73 | } 74 | 75 | // * 检测cpu使用量 76 | // @Summary Checks the cpu usage 77 | // @Description Checks the cpu usage 78 | // @Tags sd 79 | // @Accept json 80 | // @Produce json 81 | // @Success 200 {object} message "{"status":"OK", "info":"Load average: 1.82, 1.05, 0.85 | Cores: 2"}" 82 | // @Router /sd/cpu [get] 83 | func CPUCheck(c *gin.Context) { 84 | cores, _ := cpu.Counts(false) 85 | 86 | a, _ := load.Avg() 87 | l1 := a.Load1 88 | l5 := a.Load5 89 | l15 := a.Load15 90 | 91 | status := http.StatusOK 92 | text := "OK" 93 | 94 | if l5 >= float64(cores) { 95 | status = http.StatusInternalServerError 96 | text = "CRITICAL" 97 | } else if l5 >= float64(cores) { 98 | status = http.StatusTooManyRequests 99 | text = "WARNING" 100 | } 101 | 102 | mess := fmt.Sprintf("Load average: %.2f, %.2f, %.2f | Cores: %d", l1, l5, l15, cores) 103 | c.JSON(status, message{ 104 | Status: text, 105 | Info: mess, 106 | }) 107 | } 108 | 109 | // * RAM 使用量 110 | // @Summary Checks the ram usage 111 | // @Description Checks the ram usage 112 | // @Tags sd 113 | // @Accept json 114 | // @Produce json 115 | // @Success 200 {object} message "{"status":"OK", "info":"Free space: 5293MB (5GB) / 7665MB (7GB) | Used: 69%"}" 116 | // @Router /sd/ram [get] 117 | func RAMCheck(c *gin.Context) { 118 | u, _ := mem.VirtualMemory() 119 | 120 | usedMB := int(u.Used) / MB 121 | usedGB := int(u.Used) / GB 122 | totalMB := int(u.Total) / MB 123 | totalGB := int(u.Total) / GB 124 | usedPercent := int(u.UsedPercent) 125 | 126 | status := http.StatusOK 127 | text := "OK" 128 | 129 | if usedPercent >= 95 { 130 | status = http.StatusInternalServerError 131 | text = "CRITICAL" 132 | } else if usedPercent >= 90 { 133 | status = http.StatusTooManyRequests 134 | text = "WARNING" 135 | } 136 | 137 | mess := fmt.Sprintf("Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", usedMB, usedGB, totalMB, totalGB, usedPercent) 138 | c.JSON(status, message{ 139 | Status: text, 140 | Info: mess, 141 | }) 142 | } 143 | -------------------------------------------------------------------------------- /server/handler/todo.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "mini_todo/errno" 6 | "mini_todo/model" 7 | "strconv" 8 | ) 9 | 10 | type ListTodo struct { 11 | Total uint64 `json:"total"` 12 | TodoList []model.TodoModel `json:"todos"` 13 | } 14 | 15 | type createTodo struct { 16 | } 17 | 18 | /*todos 路由相关处理函数*/ 19 | 20 | // * get all todos api. 21 | // @Summary Get all todos 22 | // @Description Get all todos from database 23 | // @Tags todo 24 | // @Accept json 25 | // @Produce json 26 | // @Success 200 {object} model.TodoModel "{"code":0,"message":"OK","data":{"total":233, "todos":[{"ID":91,"title": "烫头", "completed": 1,"CreatedAt": "2019-10-12T10:10:05+08:00","UpdatedAt": "2019-10-12T10:16:24+08:00","DeletedAt": null}]}}" 27 | // @Router /v1/todos [get] 28 | func FetchAllTodo(c *gin.Context) { 29 | var todo model.TodoModel 30 | count, todos, err := todo.GetAll() 31 | if err != nil { 32 | SendResponse(c, errno.ErrDatabase, nil) 33 | return 34 | } 35 | 36 | SendResponse(c, nil, ListTodo{ 37 | Total: count, 38 | TodoList: todos, 39 | }) 40 | } 41 | 42 | // @Summary Get a todo 43 | // @Description Get a todo 44 | // @Tags todo 45 | // @Accept json 46 | // @Produce json 47 | // @Success 200 {object} model.TodoModel "{"code":0,"message":"OK","data":{"ID":91,"title": "烫头", "completed": 1,"CreatedAt": "2019-10-12T10:10:05+08:00","UpdatedAt": "2019-10-12T10:16:24+08:00","DeletedAt": null}}" 48 | // @Router /v1/todos/{id} [get] 49 | func FetchSingleTodo(c *gin.Context) { 50 | var todo model.TodoModel 51 | var err error 52 | 53 | id, _ := strconv.Atoi(c.Param("id")) 54 | todo.ID = uint(id) 55 | 56 | if todo, err = todo.Get(); err != nil { 57 | SendResponse(c, errno.ErrDatabase, nil) 58 | return 59 | } 60 | SendResponse(c, nil, todo) 61 | } 62 | 63 | // TODO title为空时错误 64 | // @Summary Add new todos to the database 65 | // @Description Add a new todo 66 | // @Tags todo 67 | // @Accept json 68 | // @Produce json 69 | // @Param todo body model.TodoModel true "The todo info" 70 | // @Success 200 {object} handler.Response "{"code":0,"message":"OK","data":"create successful."}" 71 | // @Router /v1/todos/ [post] 72 | func AddTodo(c *gin.Context) { 73 | completed, _ := strconv.Atoi(c.PostForm("completed")) 74 | 75 | todo := model.TodoModel{ 76 | Title: c.PostForm("title"), 77 | Completed: completed, 78 | } 79 | 80 | // * 创建一条记录,错误处理 81 | if err := todo.Create(); err != nil { 82 | SendResponse(c, errno.ErrDatabase, nil) 83 | return 84 | } 85 | 86 | // * 返回结果 87 | SendResponse(c, nil, "create successful.") 88 | } 89 | 90 | // @Summary Update a todo 91 | // @Description Update a todo 92 | // @Tags todo 93 | // @Accept json 94 | // @Produce json 95 | // @Param id path uint64 true "The todo's database id index num" 96 | // @Param todo body model.TodoModel true "The todo info" 97 | // @Success 200 {object} handler.Response "{"code":0,"message":"OK","data":"update successful."}" 98 | // @Router /v1/todos/{id} [put] 99 | func UpdateTodo(c *gin.Context) { 100 | id, _ := strconv.Atoi(c.Param("id")) 101 | completed, _ := strconv.Atoi(c.PostForm("completed")) 102 | todo := model.TodoModel{ 103 | Title: c.PostForm("title"), 104 | Completed: completed, 105 | } 106 | todo.ID = uint(id) 107 | if err := todo.Update(); err != nil { 108 | SendResponse(c, errno.ErrDatabase, nil) 109 | return 110 | } 111 | 112 | // * 返回结果 113 | SendResponse(c, nil, "update successful.") 114 | } 115 | 116 | // @Summary Delete a todo 117 | // @Description Delete a todo 118 | // @Tags todo 119 | // @Accept json 120 | // @Produce json 121 | // @Success 200 {object} handler.Response "{"code":0,"message":"OK","data":"delete successful."}" 122 | // @Router /v1/todos/{id} [delete] 123 | func DeleteTodo(c *gin.Context) { 124 | id, _ := strconv.Atoi(c.Param("id")) 125 | todo := model.TodoModel{} 126 | 127 | todo.ID = uint(id) 128 | 129 | if err := todo.Delete(); err != nil { 130 | SendResponse(c, errno.ErrDatabase, nil) 131 | return 132 | } 133 | SendResponse(c, nil, "delete successful.") 134 | } 135 | -------------------------------------------------------------------------------- /server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "github.com/gin-gonic/gin" 6 | _ "github.com/jinzhu/gorm/dialects/mysql" 7 | "github.com/spf13/pflag" 8 | "github.com/spf13/viper" 9 | "log" 10 | "mini_todo/config" 11 | "mini_todo/model" 12 | "mini_todo/router" 13 | "net/http" 14 | "time" 15 | ) 16 | 17 | // * 读取命令行配置(config文件) 18 | var cfg = pflag.StringP("config", "c", "", "apiserver config file path.") 19 | 20 | // @title A todos application API 21 | // @version 1.0 22 | // @description This is a todos application server. 23 | // @termsOfService http://me.shiniao.fun/ 24 | 25 | // @contact.name shiniao 26 | // @contact.url http://me.shiniao.fun/ 27 | // @contact.email zhuzhezhe5@gmail.com 28 | 29 | // @license.name Apache 2.0 30 | // @license.url http://www.apache.org/licenses/LICENSE-2.0.html 31 | 32 | // @host todo.shiniao.fun 33 | // @BasePath /v1 34 | func main() { 35 | 36 | // * 初始化配置 37 | pflag.Parse() 38 | if err := config.Init(*cfg); err != nil { 39 | panic(err) 40 | } 41 | log.Printf("The config inited.") 42 | 43 | // * 初始化数据库 44 | model.DB.Init() 45 | defer model.DB.Close() 46 | log.Printf("The database inited.") 47 | 48 | // * 设置 run mode 49 | gin.SetMode(viper.GetString("runmode")) 50 | log.Printf("Gin run mode set to: %s", viper.GetString("runmode")) 51 | 52 | // * gin engine. 53 | g := gin.New() 54 | router.Load(g) 55 | log.Printf("The gin engine started, and the router loaded.") 56 | 57 | go func() { 58 | if err := pingServer(); err != nil { 59 | log.Fatal("The router has no response, or it might took too long to start up.", err) 60 | } 61 | log.Println("The router has been deployed successfully.") 62 | }() 63 | 64 | log.Printf("Start to listening on: %s", viper.GetString("addr")) 65 | g.Run(viper.GetString("addr")).Error() 66 | } 67 | 68 | // * api 状态自检 69 | func pingServer() error { 70 | for i := 0; i < viper.GetInt("max_ping_count"); i++ { 71 | resp, err := http.Get(viper.GetString("url") + "/sd/health") 72 | if err == nil && resp.StatusCode == 200 { 73 | return nil 74 | } 75 | 76 | log.Print("Waiting for the router, retry in 1 second.") 77 | time.Sleep(time.Second) 78 | } 79 | return errors.New("Cannot connect to the router.") 80 | } 81 | -------------------------------------------------------------------------------- /server/middleware/middleware.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "mini_todo/errno" 6 | "mini_todo/handler" 7 | "mini_todo/token" 8 | "net/http" 9 | "time" 10 | ) 11 | 12 | func AuthMiddleware() gin.HandlerFunc { 13 | return func(c *gin.Context) { 14 | // Parse the json web token. 15 | if err := token.ParseRequest(c); err != nil { 16 | handler.SendResponse(c, errno.ErrTokenInvalid, nil) 17 | c.Abort() 18 | return 19 | } 20 | 21 | c.Next() 22 | } 23 | } 24 | 25 | // * 不使用cache 26 | func NoCache(c *gin.Context) { 27 | c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") 28 | c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") 29 | c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) 30 | c.Next() 31 | } 32 | 33 | // * 浏览器跨域 34 | func Options(c *gin.Context) { 35 | c.Writer.Header().Set("Access-Control-Allow-Origin", "*") 36 | c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") 37 | c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") 38 | c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT") 39 | 40 | if c.Request.Method == "OPTIONS" { 41 | c.AbortWithStatus(204) 42 | return 43 | } 44 | c.Next() 45 | 46 | } 47 | 48 | // * 安全方面设置 49 | func Secure(c *gin.Context) { 50 | c.Header("Access-Control-Allow-Origin", "*") 51 | c.Header("X-Frame-Options", "DENY") 52 | c.Header("X-Content-Type-Options", "nosniff") 53 | c.Header("X-XSS-Protection", "1; mode=block") 54 | if c.Request.TLS != nil { 55 | c.Header("Strict-Transport-Security", "max-age=31536000") 56 | } 57 | 58 | // Also consider adding Content-Security-Policy headers 59 | // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") 60 | } 61 | -------------------------------------------------------------------------------- /server/model/init.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jinzhu/gorm" 6 | _ "github.com/jinzhu/gorm/dialects/mysql" 7 | "github.com/spf13/viper" 8 | "log" 9 | ) 10 | 11 | type Database struct { 12 | Self *gorm.DB 13 | } 14 | 15 | 16 | func (db *Database) Init() { 17 | 18 | // * 初始化数据库 19 | config := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s", 20 | viper.GetString("db.username"), 21 | viper.GetString("db.password"), 22 | viper.GetString("db.addr"), 23 | viper.GetString("db.dbname"), 24 | true, 25 | //"Asia/Shanghai"), 26 | "Local") 27 | // * 以config打开mysql数据库 28 | _db, err := gorm.Open("mysql", config) 29 | DB = &Database{Self: _db} 30 | if err != nil { 31 | log.Printf("Database connection failed. Database name: %s", viper.GetString("db.dbname")) 32 | } 33 | // * 解决中文字符问题:Error 1366 34 | _db = _db.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8 auto_increment=1") 35 | _db.AutoMigrate(&TodoModel{}) 36 | 37 | } 38 | 39 | func (db *Database) Close() { 40 | // * 关闭数据库 41 | db.Self.Close().Error() 42 | } 43 | 44 | var DB *Database 45 | -------------------------------------------------------------------------------- /server/model/todo.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/jinzhu/gorm" 4 | 5 | // * bind orm 6 | type TodoModel struct { 7 | gorm.Model 8 | Title string `gorm:"not null" json:"title"` 9 | Completed int `json:"completed"` 10 | } 11 | 12 | 13 | 14 | // * 设置表名 15 | func (todo TodoModel) TableName() string { 16 | return "todos" 17 | } 18 | 19 | // * 添加todo到数据库 20 | func (todo TodoModel) Create() error { 21 | return DB.Self.Create(&todo).Error 22 | } 23 | 24 | // * 删除某个todo 25 | func (todo TodoModel) Delete() error { 26 | return DB.Self.Delete(&todo).Error 27 | } 28 | 29 | // * Update 30 | func (todo TodoModel) Update() error { 31 | return DB.Self.Save(&todo).Error 32 | } 33 | 34 | // * 获取某一条todo 35 | func (todo TodoModel) Get() (TodoModel, error) { 36 | return todo, DB.Self.First(&todo, todo.ID).Error 37 | } 38 | 39 | func (todo TodoModel) GetAll() (uint64, []TodoModel, error) { 40 | 41 | var todos []TodoModel 42 | var count uint64 43 | 44 | if err := DB.Self.Table(todo.TableName()).Count(&count).Error; err != nil { 45 | return count, todos, err 46 | } 47 | if err := DB.Self.Find(&todos).Error; err != nil { 48 | return count, todos, err 49 | } 50 | return count, todos, nil 51 | 52 | } 53 | -------------------------------------------------------------------------------- /server/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/swaggo/gin-swagger" 6 | "github.com/swaggo/gin-swagger/swaggerFiles" 7 | 8 | _ "mini_todo/docs" 9 | "mini_todo/handler" 10 | "mini_todo/middleware" 11 | "net/http" 12 | ) 13 | 14 | func Load(g *gin.Engine) *gin.Engine { 15 | 16 | // * 全局middlewares 17 | 18 | g.Use(gin.Recovery()) 19 | g.Use(middleware.NoCache) 20 | g.Use(middleware.Options) 21 | g.Use(middleware.Secure) 22 | 23 | // * 404 handler 24 | g.NoRoute(func(c *gin.Context) { 25 | c.String(http.StatusNotFound, "The incorrect API route.") 26 | }) 27 | 28 | // swagger api docs 29 | g.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) 30 | 31 | // * token下发 32 | g.POST("/token", handler.Token) 33 | 34 | // * todos 路由组 35 | v1 := g.Group("/v1/todos") 36 | v1.Use(middleware.AuthMiddleware()) 37 | { 38 | v1.GET("/", handler.FetchAllTodo) 39 | v1.GET("/:id", handler.FetchSingleTodo) 40 | v1.POST("/", handler.AddTodo) 41 | v1.PUT("/:id", handler.UpdateTodo) 42 | v1.DELETE("/:id", handler.DeleteTodo) 43 | } 44 | 45 | // * api server 健康状况 46 | svcd := g.Group("/sd") 47 | 48 | { 49 | svcd.GET("/health", handler.HealthCheck) 50 | svcd.GET("/disk", handler.DiskCheck) 51 | svcd.GET("/cpu", handler.CPUCheck) 52 | svcd.GET("/ram", handler.RAMCheck) 53 | } 54 | return g 55 | } 56 | -------------------------------------------------------------------------------- /server/token/token.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/dgrijalva/jwt-go" 7 | "github.com/gin-gonic/gin" 8 | "github.com/spf13/viper" 9 | "time" 10 | ) 11 | 12 | type Token struct { 13 | Token string `json:"token"` 14 | } 15 | 16 | var ErrMissingHeader = errors.New("The length of the `Authorization` header is zero.") 17 | 18 | 19 | // * 下发token 20 | func Sign(ctx *gin.Context, key, secret string) (tokenString string, err error) { 21 | 22 | // * 读取config 23 | if secret == "" { 24 | secret = viper.GetString("jwt_secret") 25 | } 26 | // * jwt claims 27 | // TODO 过期时间处理 28 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ 29 | // * 用户申请token携带的key 30 | "key": key, 31 | // * 生效时间 32 | "nbf": time.Now().Unix(), 33 | // * 签发时间 34 | "iat": time.Now().Unix(), 35 | // * 过期时间 36 | //"exp": time.Now().Add(time.Hour * 2).Unix(), 37 | }) 38 | // * Sign the token with the specified secret. 39 | tokenString, err = token.SignedString([]byte(secret)) 40 | 41 | return 42 | } 43 | 44 | 45 | func ParseRequest(c *gin.Context) error { 46 | header := c.Request.Header.Get("Authorization") 47 | 48 | // * Load the jwt secret from config 49 | secret := viper.GetString("jwt_secret") 50 | 51 | if len(header) == 0 { 52 | return ErrMissingHeader 53 | } 54 | 55 | var t string 56 | // * Parse the header to get the token part. 57 | fmt.Sscanf(header, "Bearer %s", &t) 58 | return Parse(t, secret) 59 | } 60 | 61 | 62 | // * 解析token 63 | func Parse(token, secret string) error { 64 | 65 | // * parse the token 66 | t, err := jwt.Parse(token, secretFunc(secret)) 67 | 68 | // Parse error. 69 | if err != nil { 70 | return err 71 | 72 | // * Read the token if it's valid. 73 | } else if _, ok := t.Claims.(jwt.MapClaims); ok && t.Valid { 74 | return nil 75 | 76 | } else { 77 | return err 78 | } 79 | } 80 | 81 | func secretFunc(secret string) jwt.Keyfunc { 82 | return func(token *jwt.Token) (interface{}, error) { 83 | // * Make sure the `alg` is what we except. 84 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { 85 | return nil, jwt.ErrSignatureInvalid 86 | } 87 | 88 | return []byte(secret), nil 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /todogif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ischaojie/gtodo/6f4b39ae6aea83158943b22ad2119d9205465354/todogif.gif --------------------------------------------------------------------------------