├── .env ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── example.env ├── gapp.run.sh ├── go.mod ├── go.sum ├── main.go ├── models ├── base_model.go ├── model.go └── sass │ ├── users.go │ ├── users_accounts.go │ ├── users_accounts_test.go │ └── users_test.go ├── pkg └── util │ └── api.go ├── resources └── themes │ ├── default │ └── simple │ │ └── get.tmpl │ └── default_mobile │ └── simple │ └── get.tmpl ├── routers ├── api │ └── v1 │ │ └── demo │ │ ├── demo_user_struct.go │ │ ├── gorm.go │ │ ├── gorm_raw_sql.go │ │ ├── simple.go │ │ └── validator.go ├── api_router.go ├── app │ └── v1 │ │ └── demo │ │ └── simple_html.go ├── app_router.go └── router.go └── var └── log └── .gitignore /.env: -------------------------------------------------------------------------------- 1 | 2 | # 数据库配置 3 | DB_DRIVER="mysql"# 驱动类型: mysql, postgres, sqlite3, mssql 4 | DB_HOST="199.199.199.199" # 数据库 5 | DB_PORT="3306" #数据库端口 6 | DB_CHARSET="UTF8" # 数据库编码 7 | DB_DATABASE="gapp" # 数据名 8 | DB_USERNAME="root3" # 数据库用户名 9 | DB_PASSWORD="root3" # 数据库用户密码 10 | DB_MAX_CONNECTIONS="100" # 最大连接数 11 | DB_MAX_OPEN_CONNECTIONS="10" # 打开连接数 12 | DB_LOG=true # 是否sql启用日志: true->启动日志, 否则不开启 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.out 3 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 zhao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | appName ?= "gapp" 2 | echo: 3 | @echo $(appName) 4 | win32: 5 | @CGO_ENABLED=0 GOOS=windows GOARCH=386 GO111MODULE=on GOPROXY=https://goproxy.io go build -o bin/win/32/$(appName).exe 6 | win64: 7 | @CGO_ENABLED=0 GOOS=windows GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.io go build -o bin/win/64/$(appName).exe 8 | mac32: 9 | @CGO_ENABLED=0 GOOS=darwin GOARCH=386 GO111MODULE=on GOPROXY=https://goproxy.io go build -o bin/mac/32/$(appName) 10 | mac64: 11 | @CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.io go build -o bin/mac/64/$(appName) 12 | linux32: 13 | @CGO_ENABLED=0 GOOS=linux GOARCH=386 GO111MODULE=on GOPROXY=https://goproxy.io go build -o bin/linux/32/$(appName) 14 | linux64: 15 | @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.io go build -o bin/linux/64/$(appName) 16 | clear: 17 | @rm -rf bin/mac/* 18 | @rm -rf bin/linux/* 19 | @rm -rf bin/win/* 20 | all: clear win32 win64 mac32 mac64 linux32 linux64 21 | dev: 22 | @GO111MODULE=on GOPROXY=https://goproxy.io go run -race main.go -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gapp 2 | go gin框架应用脚手架, 帮助你快速搭建golang项目 3 | 4 | | 名称|是否支持| 5 | |---------:|:------| 6 | | 数据库|支持 | 7 | | 日志分割|支持 | 8 | | .env配置|支持 | 9 | | 多主题(themes)模板|支持 | 10 | | 多版本api|支持 | 11 | | 多版本app|支持 | 12 | 13 | 14 | ## 开发命令 15 | ```sh 16 | # 编译所有平台二进制可执行文件 17 | make all 18 | 19 | # 编译所有平台二进制可执行文件,并制定可执行文件名 20 | make all appName=devGapp 21 | make all appName=stageGapp 22 | make all appName=gapp 23 | 24 | # 开发时运行 25 | make dev 26 | 27 | # 清空编译二进制文件 28 | make clear 29 | ``` 30 | 31 | ## gapp使用说明 32 | > 1. gapp使用了那些golang第三方库 33 | > 2. gapp目录结构 34 | > 3. 安装gapp 35 | > 4. gapp配置 36 | > 5. 运行gapp 37 | > 6. 示例文件 38 | 39 | ### 1. gapp使用了那些第三方库 40 | > [**框架: gin**](https://github.com/gin-gonic/gin) -> [文档](https://gin-gonic.com/zh-cn/docs/) 41 | 42 | > [**数据库: gorm**](https://github.com/jinzhu/gorm) -> [文档](http://gorm.io/zh_CN/docs/) 43 | 44 | > [**.env配置: godotenv**](https://github.com/joho/godotenv) -> [文档](https://github.com/joho/godotenv) 45 | 46 | 47 | ### 2. gapp目录结构 48 | ```php 49 | gapp 应用根目录 50 | ├─dev 开发者存放项目相关开发信息 51 | ├─gapp 应用根目录 52 | │ ├─langs 核心语言包目录 53 | │ ├─models 模型目录 54 | │ ├─pkg 公共库目录 55 | │ │ ├─util 通用包目录 56 | │ ├─resources 资源目录 57 | │ │ ├─themes 主题(themes)目录 58 | │ │ │ ├─default 默认主题(default)目录 59 | │ │ │ ├─default_mobile 默认手机主题(default_mobile)目录 60 | │ ├─routers 路由目录 61 | │ │ ├─api_router.go 接口路由配置文件 62 | │ │ ├─app_router.go 网页应用路由配置文件 63 | │ │ ├─router.go 路由配置文件 64 | │ │ ├─api 接口目录 65 | │ │ │ ├─v1 v1接口开发目录 66 | │ │ │ │ ├─demo 示例接口目录 67 | │ │ │ │ │ ├─simple.go 简单请求示例 68 | │ │ │ │ │ ├─gorm.go gorm操作数据库(db)示例 69 | │ │ │ │ │ ├─gorm_raw_sql.go gorm raw sql 原生sql操作数据库(db)示例 70 | │ │ │ ├─ ... vn接口开发目录 71 | │ │ ├─app 网页应用目录 72 | │ │ │ ├─v1 v1网页应用开发目录 73 | │ │ │ │ ├─demo 示例网页应用开发目录 74 | │ │ │ │ │ ├─simple_html.go 简单网页应用示例 75 | │ │ │ ├─ ... vn网页应用开发目录 76 | │ │ ├─var 变量目录(其内容在系统正常运行期间会不断更改的文件) 77 | │ │ │ ├─log 日志存放目录 78 | │ │ ├─Vendor 第三方类库目录 79 | │ ├─.env 配置文件 80 | │ ├─example.env 示例配置文件 81 | │ ├─LICENSE.txt 授权协议文件 82 | │ ├─Makefile makefile文件 83 | │ ├─README.txt README文件 84 | │ └─main.go 入口文件 85 | ``` 86 | 87 | ### 3. 安装gapp 88 | > 1. 查看GOPATH路径: go env 命令查看 GOPATH 路径 89 | > 2. 下载gapp: git clone https://github.com/qq1060656096/gapp.git 90 | > 3. sql导入MySQL数据库 91 | ```sql 92 | DROP TABLE IF EXISTS `demo_user`; 93 | CREATE TABLE `demo_user` ( 94 | `id` int(11) NOT NULL AUTO_INCREMENT, 95 | `user` varchar(255) CHARACTER SET utf8mb4 NOT NULL DEFAULT '', 96 | `pass` varchar(255) CHARACTER SET utf8mb4 NOT NULL DEFAULT '', 97 | PRIMARY KEY (`id`) 98 | ) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 99 | 100 | -- ---------------------------- 101 | -- Table structure for user 102 | -- ---------------------------- 103 | DROP TABLE IF EXISTS `user`; 104 | CREATE TABLE `user` ( 105 | `id` int(11) NOT NULL DEFAULT '0', 106 | `name` varchar(255) CHARACTER SET utf8mb4 NOT NULL DEFAULT '' 107 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 108 | 109 | -- ---------------------------- 110 | -- Table structure for users 111 | -- ---------------------------- 112 | DROP TABLE IF EXISTS `users`; 113 | CREATE TABLE `users` ( 114 | `uid` int(11) unsigned NOT NULL AUTO_INCREMENT, 115 | `pass` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码', 116 | `created_at` int(20) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', 117 | `updated_at` int(20) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', 118 | `deleted_at` int(20) unsigned NOT NULL DEFAULT '0' COMMENT '删除时间', 119 | PRIMARY KEY (`uid`) 120 | ) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 121 | 122 | -- ---------------------------- 123 | -- Table structure for users_accounts 124 | -- ---------------------------- 125 | DROP TABLE IF EXISTS `users_accounts`; 126 | CREATE TABLE `users_accounts` ( 127 | `uid` int(11) unsigned NOT NULL, 128 | `account_name` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '账户', 129 | `account_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '账户类型: 1->手机号码, 2 -> 邮箱, 3 -> 微信', 130 | `created_at` int(20) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', 131 | `updated_at` int(20) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', 132 | `deleted_at` int(20) unsigned NOT NULL DEFAULT '0' COMMENT '删除时间', 133 | UNIQUE KEY `uniq_uaa` (`uid`,`account_name`,`account_type`,`deleted_at`) USING BTREE COMMENT '唯一索引', 134 | UNIQUE KEY `uniq_index` (`account_name`,`account_type`,`deleted_at`) USING BTREE COMMENT '唯一索引', 135 | KEY `idx_uid` (`uid`) USING BTREE COMMENT '用户uid' 136 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 137 | ``` 138 | 139 | ### 4. 配置文件 140 | > 1. 进入目录: cd gapp 141 | > 2. 创建配置: cp example.env .env 142 | > 3. 配置数据 143 | 144 | ### 5. 运行gapp 145 | ```sh 146 | # 运行gapp 147 | bin/mac/64/gapp 148 | ``` 149 | 150 | ### 6. gapp示例 151 | 152 | ``` 153 | # gorm model操作数据库 154 | gapp/routers/api/v1/demo/gorm.go 155 | 156 | # gorm 执行原生sql 157 | gapp/routers/api/v1/demo/gorm_raw_sql.go 158 | 159 | # 接口演示get post put delete 160 | gapp/routers/api/v1/demo/simple.go 161 | 162 | # 网页模板示例 163 | gapp/routers/app/v1/demo/simple_html.go 164 | ``` 165 | 166 | -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | 2 | # 数据库配置 3 | DB_DRIVER="mysql"# 驱动类型: mysql, postgres, sqlite3, mssql 4 | DB_HOST="199.199.199.199" # 数据库 5 | DB_PORT="3306" #数据库端口 6 | DB_CHARSET="UTF8" # 数据库编码 7 | DB_DATABASE="gapp" # 数据名 8 | DB_USERNAME="root3" # 数据库用户名 9 | DB_PASSWORD="root3" # 数据库用户密码 10 | DB_MAX_CONNECTIONS="100" # 最大连接数 11 | DB_MAX_OPEN_CONNECTIONS="10" # 打开连接数 12 | DB_LOG=true # 是否sql启用日志: true->启动日志, 否则不开启 -------------------------------------------------------------------------------- /gapp.run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SERVER="gapp" 4 | CMD_DIR=$PWD 5 | 6 | function start() 7 | { 8 | if [ "`pgrep ${SERVER}`" != "" ] 9 | then 10 | echo "${SERVER} already running" 11 | exit 1 12 | fi 13 | 14 | nohup ${CMD_DIR}/${SERVER} &>"./var/log/nohup.$SERVER.log" & 15 | 16 | if [ "`pgrep ${SERVER}`" == "" ] 17 | then 18 | echo "${SERVER} start failed." 19 | exit 1 20 | fi 21 | } 22 | 23 | 24 | 25 | function stop() 26 | { 27 | if [ "`pgrep ${SERVER} -u ${UID}`" != "" ] 28 | then 29 | kill -9 `pgrep $SERVER -u $UID` 30 | exit 1 31 | fi 32 | 33 | if [[ "`pgrep ${SERVER} -u ${UID}`" != "" ]] 34 | then 35 | echo "${SERVER} stop failed" 36 | exit 1 37 | fi 38 | } 39 | 40 | function status() 41 | { 42 | if [[ "`pgrep ${SERVER} -u ${UID}`" != "" ]];then 43 | echo "${SERVER} is running" 44 | else 45 | echo ${SERVER} is not running 46 | fi 47 | } 48 | 49 | 50 | case "$1" in 51 | 'start') 52 | start 53 | ;; 54 | 'stop') 55 | stop 56 | ;; 57 | 'restart') 58 | stop && start 59 | ;; 60 | 'status') 61 | status 62 | ;; 63 | *) 64 | echo "usage: $0 {start|stop|restart|status}" 65 | exit 1 66 | ;; 67 | esac -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module gapp 2 | 3 | require ( 4 | github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf 5 | github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 6 | github.com/gin-gonic/autotls v0.0.0-20190119125636-0b5f4fc15768 7 | github.com/gin-gonic/gin v1.3.0 8 | github.com/golang/protobuf v1.2.0 9 | github.com/gomodule/redigo v0.0.0-20190322064113-39e2c31b7ca3 10 | github.com/jinzhu/gorm v0.0.0-20190310121721-8b07437717e7 11 | github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a 12 | github.com/joho/godotenv v0.0.0-20190204044109-5c0e6c6ab1a0 13 | github.com/json-iterator/go v1.1.6 14 | github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f 15 | github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect 16 | github.com/mattn/go-isatty v0.0.7 17 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd 18 | github.com/modern-go/reflect2 v1.0.1 19 | github.com/pkg/errors v0.8.1 // indirect 20 | github.com/ugorji/go v0.0.0-20190320090025-2dc34c0b8780 21 | gopkg.in/go-playground/validator.v8 v8.18.2 22 | gopkg.in/yaml.v2 v2.2.2 23 | ) 24 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.36.0/go.mod h1:RUoy9p/M4ge0HzT8L+SDZ8jg+Q6fth0CiBuhFJpSV40= 4 | dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= 5 | dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= 6 | dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= 7 | dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= 8 | git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= 9 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 10 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= 11 | github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco= 12 | github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 13 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 14 | github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= 15 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 16 | github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 17 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 19 | github.com/denisenkom/go-mssqldb v0.0.0-20190204142019-df6d76eb9289/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= 20 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 21 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= 22 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= 23 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 24 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 25 | github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= 26 | github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= 27 | github.com/gin-gonic/autotls v0.0.0-20190119125636-0b5f4fc15768/go.mod h1:tEDoOs55+VudM/14kaK6CTZXkI8+wH/0ymxhgk6HJlQ= 28 | github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= 29 | github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 30 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 31 | github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 32 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 33 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 34 | github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= 35 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 36 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 37 | github.com/golang/protobuf v0.0.0-20190318194812-d3c38a4eb497/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 38 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 39 | github.com/gomodule/redigo v0.0.0-20190322064113-39e2c31b7ca3/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= 40 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 41 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 42 | github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= 43 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 44 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 45 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 46 | github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= 47 | github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= 48 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 49 | github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 50 | github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= 51 | github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= 52 | github.com/jinzhu/gorm v0.0.0-20190310121721-8b07437717e7 h1:BqV/Fptq87r71Sa4q98huUVPaGQf7dYgpX3rufzl/Lg= 53 | github.com/jinzhu/gorm v0.0.0-20190310121721-8b07437717e7/go.mod h1:K3FfvCLYvREvqwtVPVDRHewgkMRcOUtbPf9kZM/uXXI= 54 | github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 55 | github.com/jinzhu/now v1.0.0/go.mod h1:oHTiXerJ20+SfYcrdlBO7rzZRJWGwSTQ0iUY2jI6Gfc= 56 | github.com/joho/godotenv v0.0.0-20190204044109-5c0e6c6ab1a0 h1:rC6SMh3N4+89smbp92lbvL3NJSVj7Oul9VWc7YXTbgk= 57 | github.com/joho/godotenv v0.0.0-20190204044109-5c0e6c6ab1a0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 58 | github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 59 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 60 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 61 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 62 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 63 | github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 64 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 65 | github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f h1:sgUSP4zdTUZYZgAGGtN5Lxk92rK+JUFOwf+FT99EEI4= 66 | github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f/go.mod h1:UGmTpUd3rjbtfIpwAPrcfmGf/Z1HS95TATB+m57TPB8= 67 | github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 h1:Bvq8AziQ5jFF4BHGAEDSqwPW1NJS3XshxbRCxtjFAZc= 68 | github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0= 69 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 70 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 71 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 72 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 73 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 74 | github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= 75 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 76 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 77 | github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= 78 | github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= 79 | github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= 80 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 81 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 82 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 83 | github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 84 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 85 | github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 86 | github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 87 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 88 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 89 | github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= 90 | github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= 91 | github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= 92 | github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= 93 | github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= 94 | github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= 95 | github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= 96 | github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= 97 | github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= 98 | github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= 99 | github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= 100 | github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= 101 | github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= 102 | github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= 103 | github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= 104 | github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= 105 | github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= 106 | github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= 107 | github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= 108 | github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 109 | github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= 110 | github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= 111 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= 112 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= 113 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 114 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 115 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 116 | github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= 117 | github.com/ugorji/go v0.0.0-20190320090025-2dc34c0b8780 h1:7GrY+ZYRWV/2fKMI6xmHLRwa7AEwCInlTa4fLBXs+uk= 118 | github.com/ugorji/go v0.0.0-20190320090025-2dc34c0b8780/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= 119 | github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 120 | go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= 121 | go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= 122 | golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= 123 | golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 124 | golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 125 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 126 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 127 | golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 128 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 129 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 130 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 131 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 132 | golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 133 | golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 134 | golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 135 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 136 | golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 137 | golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 138 | golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= 139 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 140 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 141 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 142 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 143 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 144 | golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 145 | golang.org/x/sys v0.0.0-20190116161447-11f53e031339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 146 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 147 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= 148 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 149 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 150 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 151 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 152 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 153 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 154 | golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 155 | google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= 156 | google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= 157 | google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= 158 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 159 | google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 160 | google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 161 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 162 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 163 | google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 164 | google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 165 | google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= 166 | google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4= 167 | google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 168 | google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= 169 | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= 170 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 171 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= 172 | gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= 173 | gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= 174 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 175 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 176 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 177 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 178 | grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= 179 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 180 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 181 | sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= 182 | sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= 183 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "gapp/models" 5 | "gapp/routers" 6 | "github.com/gin-gonic/gin" 7 | "github.com/joho/godotenv" 8 | "github.com/lestrrat/go-file-rotatelogs" 9 | "log" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | godotenv.Load(".env") 15 | 16 | // 设置分割日志 17 | //logWriterFormat := "var/log/gin.%Y%m%d.%H%M.log" 18 | logWriterFormat := "var/log/gin.%Y%m%d.log" 19 | logWriter, err := rotatelogs.New( 20 | logWriterFormat, 21 | rotatelogs.WithLinkName("var/log/access_log.log"), 22 | rotatelogs.WithMaxAge(24 * time.Hour), 23 | rotatelogs.WithRotationTime(time.Hour), 24 | ) 25 | if err != nil { 26 | log.Printf("failed to create rotatelogs: %s", err) 27 | return 28 | } 29 | 30 | 31 | gin.DefaultWriter = logWriter 32 | engine := gin.Default() 33 | engine.LoadHTMLGlob("resources/themes/***/***/*") 34 | // 初始化路由 35 | routers.InitRouter(engine) 36 | // 初始化模型 37 | models.ConnectDB() 38 | engine.Run(":8080") 39 | } 40 | -------------------------------------------------------------------------------- /models/base_model.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // BaseModel 基模型定义 8 | type BaseModel struct { 9 | CreatedAt int64 `gorm:"type:int;"` 10 | UpdatedAt int64 `gorm:"type:int;"` 11 | DeletedAt int64 `gorm:"type:int;"` 12 | 13 | } 14 | 15 | func (bm *BaseModel) BeforeSave() (err error) { 16 | if bm.CreatedAt < 1 { 17 | bm.CreatedAt = time.Now().Unix() 18 | } 19 | return 20 | } 21 | 22 | func (bm *BaseModel) BeforeUpdate() (err error) { 23 | bm.UpdatedAt = time.Now().Unix() 24 | return 25 | } 26 | 27 | func (bm *BaseModel) BeforeDelete33() (err error) { 28 | bm.DeletedAt = time.Now().Unix() 29 | return 30 | } 31 | 32 | func (bm *BaseModel) AfterFind() (err error) { 33 | 34 | return 35 | } -------------------------------------------------------------------------------- /models/model.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jinzhu/gorm" 6 | _ "github.com/jinzhu/gorm/dialects/mysql" // indirect 7 | "log" 8 | "os" 9 | "strconv" 10 | ) 11 | 12 | const ( 13 | DRIVER_MY_SQL = "mysql" 14 | DRIVER_POSTGRE_SQL = "postgres" 15 | DRIVER_SQLITE3 = "sqlite3" 16 | DRIVER_SQL_SERVER = "mssql" 17 | ) 18 | 19 | var DB *gorm.DB 20 | 21 | 22 | // ConnectDB 连接数据库 23 | func ConnectDB() { 24 | driver := os.Getenv("DB_DRIVER") 25 | switch driver { 26 | case DRIVER_MY_SQL: 27 | DB = ConnectDbMySQL( 28 | os.Getenv("DB_HOST"), 29 | os.Getenv("DB_PORT"), 30 | os.Getenv("DB_DATABASE"), 31 | os.Getenv("DB_USERNAME"), 32 | os.Getenv("DB_PASSWORD"), 33 | os.Getenv("DB_CHARSET"), 34 | ) 35 | case DRIVER_POSTGRE_SQL: 36 | DB = ConnectDbPostgreSQL( 37 | os.Getenv("DB_HOST"), 38 | os.Getenv("DB_PORT"), 39 | os.Getenv("DB_DATABASE"), 40 | os.Getenv("DB_USERNAME"), 41 | os.Getenv("DB_PASSWORD"), 42 | ) 43 | case DRIVER_SQLITE3: 44 | DB = ConnectDbSqlite3( 45 | os.Getenv("DB_HOST"), 46 | ) 47 | case DRIVER_SQL_SERVER: 48 | DB = ConnectDbMySQL( 49 | os.Getenv("DB_HOST"), 50 | os.Getenv("DB_PORT"), 51 | os.Getenv("DB_DATABASE"), 52 | os.Getenv("DB_USERNAME"), 53 | os.Getenv("DB_PASSWORD"), 54 | os.Getenv("DB_CHARSET"), 55 | ) 56 | default: 57 | log.Fatalf("models.ConnectDB driver err: %s", driver) 58 | } 59 | maxConnections, _ := strconv.Atoi(os.Getenv("DB_MAX_CONNECTIONS")) 60 | openConnections, _ := strconv.Atoi(os.Getenv("DB_MAX_OPEN_CONNECTIONS")) 61 | DB.DB().SetMaxOpenConns(maxConnections) 62 | DB.DB().SetMaxIdleConns(openConnections) 63 | dbLog := os.Getenv("DB_LOG") 64 | if dbLog == "true" { 65 | DB.LogMode(true) 66 | } 67 | } 68 | 69 | // ConnectDbMySQL 初始化Mysql db 70 | func ConnectDbMySQL(host, port, database, user, pass , charset string) *gorm.DB { 71 | dns := fmt.Sprintf( 72 | "%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local", 73 | user, 74 | pass, 75 | host, 76 | port, 77 | database, 78 | charset, 79 | ) 80 | 81 | db, err := gorm.Open(DRIVER_MY_SQL, dns) 82 | if err != nil { 83 | log.Fatalf("models.InitDbMySQL err: %v", err) 84 | } 85 | db.SingularTable(true) 86 | maxConnections, _ := strconv.Atoi(os.Getenv("DB_MAX_CONNECTIONS")) 87 | openConnections, _ := strconv.Atoi(os.Getenv("DB_MAX_OPEN_CONNECTIONS")) 88 | db.DB().SetMaxOpenConns(maxConnections) 89 | db.DB().SetMaxIdleConns(openConnections) 90 | return db 91 | } 92 | 93 | // ConnectDbPostgreSQL 连接dbpostgresql数据库 94 | func ConnectDbPostgreSQL(host, port, database, user, pass string) *gorm.DB { 95 | dns := fmt.Sprintf( 96 | "host=%s port=myport user=%s dbname=%s password=%s", 97 | host, 98 | port, 99 | user, 100 | database, 101 | pass, 102 | ) 103 | 104 | db, err := gorm.Open(DRIVER_POSTGRE_SQL, dns) 105 | if err != nil { 106 | log.Fatalf("models.ConnectDbPostgreSQL err: %v", err) 107 | } 108 | db.SingularTable(true) 109 | return db 110 | } 111 | 112 | // ConnectDbSqlite3 连接sqlite3数据库 113 | func ConnectDbSqlite3(host string) *gorm.DB { 114 | dns := fmt.Sprintf( 115 | "%s", 116 | host, 117 | ) 118 | 119 | db, err := gorm.Open(DRIVER_SQLITE3, dns) 120 | if err != nil { 121 | log.Fatalf("models.ConnectDbSqlite3 err: %v", err) 122 | } 123 | db.SingularTable(true) 124 | return db 125 | } 126 | 127 | // ConnectDbSqlServer 连接 sql server数据库 128 | func ConnectDbSqlServer(host, port, database, user, pass string) *gorm.DB { 129 | dns := fmt.Sprintf( 130 | "sqlserver://%s:%s@%s:%s?database=%s", 131 | user, 132 | pass, 133 | host, 134 | port, 135 | database, 136 | ) 137 | 138 | db, err := gorm.Open(DRIVER_SQL_SERVER, dns) 139 | if err != nil { 140 | log.Fatalf("models.ConnectDbSqlServer err: %v", err) 141 | } 142 | db.SingularTable(true) 143 | return db 144 | } -------------------------------------------------------------------------------- /models/sass/users.go: -------------------------------------------------------------------------------- 1 | package sass 2 | 3 | import "gapp/models" 4 | 5 | // UsersModel 用户模型 6 | type UsersModel struct { 7 | models.BaseModel 8 | Uid uint `gorm:"primary_key" json:"uid"` 9 | Pass string `json:"pass" form:"pass" xml:"pass" binding:"required"` 10 | } 11 | 12 | // TableName 表名 13 | func (u *UsersModel) TableName() string { 14 | return "users" 15 | } 16 | 17 | // UsersCreate 创建用户 18 | func UserCreate(u *UsersModel) (bool) { 19 | rows := models.DB.Create(u).RowsAffected 20 | if (rows < 1) { 21 | return false 22 | } 23 | return true 24 | } 25 | 26 | // 用户注册 27 | func UserRegister(u *UsersModel, ua *UsersAccountsModel) bool { 28 | // 1. 创建账户 29 | // 2. 绑定用户账户 30 | b := UserCreate(u) 31 | if !b { 32 | return b 33 | } 34 | return UserBindAccount(ua) 35 | } -------------------------------------------------------------------------------- /models/sass/users_accounts.go: -------------------------------------------------------------------------------- 1 | package sass 2 | 3 | import ( 4 | "gapp/models" 5 | "time" 6 | ) 7 | 8 | const ( 9 | ACCOUNT_TYPE = iota 10 | // 手机号码 11 | ACCOUNT_TYPE_MOBILE 12 | // 邮箱 13 | ACCOUNT_TYPE_EMAIL 14 | // 微信 15 | ACCOUNT_TYPE_WECHAT 16 | ) 17 | 18 | 19 | // UsersAccountsModel 用户账户关联表 20 | type UsersAccountsModel struct { 21 | models.BaseModel 22 | Uid uint `json:"uid" xml:"uid"` 23 | AccountName string `form:"account_name" json:"account_name" xml:"account_name" binding:"required"` 24 | AccountType int `form:"account_type" json:"account_type" xml:"account_type" binding:"required"` 25 | } 26 | 27 | // TableName 表名 28 | func (ua *UsersAccountsModel) TableName() string { 29 | return "users_accounts" 30 | } 31 | 32 | // UserBindAccount 定用户绑账户 33 | func UserBindAccount(ua *UsersAccountsModel) (bool) { 34 | rows := models.DB.Create(ua).RowsAffected 35 | if (rows < 1) { 36 | return false 37 | } 38 | return true 39 | } 40 | 41 | // UserUnbindAccount 用户解绑账户 42 | func UserUnbindAccount(ua *UsersAccountsModel) bool { 43 | uaNew := UsersAccountsModel{ 44 | Uid: ua.Uid, 45 | AccountName: ua.AccountName, 46 | AccountType: ua.AccountType, 47 | } 48 | uaNew.DeletedAt = time.Now().Unix() 49 | rows := models.DB.Table(ua.TableName()). 50 | Where("uid = ?", ua.Uid). 51 | Where("account_name = ?", ua.AccountName). 52 | Where("account_type = ?", ua.AccountType). 53 | Where("deleted_at = ?", 0). 54 | Updates(uaNew).RowsAffected 55 | if (rows < 1) { 56 | return false 57 | } 58 | return true 59 | } 60 | 61 | // GetUserBindAccount 获取用户绑定指定类型账户 62 | func GetUserBindAccount(ua *UsersAccountsModel) *UsersAccountsModel { 63 | uaNew := &UsersAccountsModel{ 64 | Uid: 0, 65 | AccountName: "", 66 | } 67 | models.DB.Table(ua.TableName()). 68 | Where("uid = ?", ua.Uid). 69 | Where("account_name = ?", ua.AccountName). 70 | Where("account_type = ?", ua.AccountType). 71 | Where("deleted_at = ?", 0). 72 | Unscoped(). 73 | First(&uaNew) 74 | return uaNew 75 | } 76 | 77 | // GetUserBindAllAccounts 获取用户绑定的账户列表 78 | func GetUserBindAllAccounts(uid uint) []*UsersAccountsModel { 79 | ua := []*UsersAccountsModel{} 80 | models.DB.Where("uid = ?", uid). 81 | Where("deleted_at = ?", 0). 82 | Unscoped(). 83 | Find(&ua) 84 | return ua 85 | } -------------------------------------------------------------------------------- /models/sass/users_accounts_test.go: -------------------------------------------------------------------------------- 1 | package sass 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | var testStruct = map[string]UsersAccountsModel{ 9 | "test1": { 10 | Uid: 1, 11 | AccountName: "test1@qq.com", 12 | AccountType: ACCOUNT_TYPE_EMAIL, 13 | }, 14 | "test2": { 15 | Uid: 1, 16 | AccountName: "15400000010", 17 | AccountType: ACCOUNT_TYPE_MOBILE, 18 | }, 19 | } 20 | 21 | // TestUserBindAccount 测试绑定账户 22 | func TestUserBindAccount(t *testing.T) { 23 | ua := testStruct["test1"] 24 | UserUnbindAccount(&ua) 25 | b := UserBindAccount(&ua) 26 | assert.Equal(t, true, b, "sass.TestGetUserBindAccount test fail") 27 | 28 | ua = testStruct["test2"] 29 | UserUnbindAccount(&ua) 30 | UserBindAccount(&ua) 31 | } 32 | 33 | // TestGetUserBindAccount 测试获取绑定账户 34 | func TestGetUserBindAccount(t *testing.T) { 35 | ua := testStruct["test1"] 36 | fua := GetUserBindAccount(&ua) 37 | assert.Equal(t, ua.Uid, fua.Uid, "sass.GetUserBindAccount test fail") 38 | assert.Equal(t, "test1@qq.com", fua.AccountName, "sass.GetUserBindAccount test fail") 39 | } 40 | 41 | 42 | // TestGetUserBindAllAccounts 测试用户绑定了那些账户 43 | func TestGetUserBindAllAccounts(t *testing.T) { 44 | uaList := GetUserBindAllAccounts(1) 45 | assert.Equal(t, 1, uaList[0].AccountType) 46 | assert.Equal(t, "15400000010", uaList[0].AccountName) 47 | assert.Equal(t, "test1@qq.com", uaList[1].AccountName) 48 | 49 | } -------------------------------------------------------------------------------- /models/sass/users_test.go: -------------------------------------------------------------------------------- 1 | package sass 2 | 3 | import ( 4 | "fmt" 5 | "gapp/models" 6 | "github.com/joho/godotenv" 7 | "github.com/stretchr/testify/assert" 8 | "os" 9 | "path/filepath" 10 | "testing" 11 | ) 12 | 13 | func init() { 14 | dir, _ := os.Getwd() 15 | dir = (filepath.Dir(filepath.Dir(dir))) 16 | testEnv := dir + "/.env" 17 | fmt.Println(testEnv) 18 | godotenv.Load(testEnv) 19 | models.ConnectDB() 20 | } 21 | 22 | var userTestStruct = map[string]UsersAccountsModel{ 23 | "test2User": { 24 | Uid: 2, 25 | AccountName: "test2@qq.com", 26 | AccountType: ACCOUNT_TYPE_EMAIL, 27 | }, 28 | "test2UserAccount": { 29 | Uid: 2, 30 | AccountName: "15400000012", 31 | AccountType: ACCOUNT_TYPE_MOBILE, 32 | }, 33 | } 34 | 35 | // TestUserCreate 测试用户创建 36 | func TestUserCreate(t *testing.T) { 37 | var u *UsersModel 38 | u = &UsersModel{ 39 | Pass: "123456", 40 | } 41 | b := UserCreate(u) 42 | assert.Equal(t, true, b, "sass.UserCreate Test fail") 43 | assert.Equal(t, true, u.Uid > 0, "sass.UserCreate Test fail") 44 | } 45 | 46 | func TestUsersRegister(t *testing.T) { 47 | u := &UsersModel{ 48 | Uid: 2, 49 | Pass: "p123456", 50 | } 51 | ua := &UsersAccountsModel{ 52 | Uid: 2, 53 | AccountName: "test2@qq.com", 54 | AccountType: ACCOUNT_TYPE_EMAIL, 55 | } 56 | models.DB.Table(u.TableName()).Where("uid = 2").Unscoped().Delete(&UsersModel{}) 57 | models.DB.Table(ua.TableName()).Where("uid = 2").Unscoped().Delete(&UsersAccountsModel{}) 58 | b := UserRegister(u, ua) 59 | assert.Equal(t, true, b, "sass.TestUsersRegister test fail") 60 | } -------------------------------------------------------------------------------- /pkg/util/api.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "math" 4 | 5 | // ApiJsonResult 接口json返回值 6 | type ApiJsonResult struct { 7 | Code string `json:"code"` 8 | Message string `json:"message"` 9 | Data interface{} `json:"data"` 10 | } 11 | 12 | type ApiPagingJsonResult struct { 13 | TotalCount int `json:"totalCount"` 14 | TotalPageCount int `json:"totalPageCount"` 15 | Page int `json:"page"` 16 | PageSize int `json:"pageSize"` 17 | Lists interface{} `json:"lists"` 18 | } 19 | // NewApiJsonResult 创建 ApiJsonResult 20 | func NewApiJsonResult(code string, message string) *ApiJsonResult { 21 | return &ApiJsonResult{ 22 | Code: code, 23 | Message: message, 24 | } 25 | } 26 | 27 | // Simple 普通数据返回 28 | func (o *ApiJsonResult) Simple (data interface{}) *ApiJsonResult { 29 | o.Data = data 30 | return o 31 | } 32 | 33 | // Paging 分页 34 | func (o *ApiJsonResult) Paging(lists interface{}, totalCount, page int, pageSize int) *ApiJsonResult { 35 | totalPageCount := 0 36 | if pageSize > 0 { 37 | totalPageCount = int(math.Ceil(float64(totalCount/pageSize))) 38 | } 39 | o.Data = ApiPagingJsonResult{ 40 | TotalCount: totalCount, 41 | TotalPageCount: totalPageCount, 42 | Page: page, 43 | PageSize: pageSize, 44 | Lists: lists, 45 | } 46 | return o 47 | } 48 | 49 | // GetApiJsonResult 获取接口json返回值 50 | func GetApiJsonResult(code string, message string, data interface{}) *ApiJsonResult { 51 | return &ApiJsonResult{ 52 | Code: code, 53 | Data: data, 54 | Message: message, 55 | } 56 | } 57 | 58 | // GetApiJsonPagingResult 获取接口分页json返回值 59 | func GetApiJsonPagingResult(code string, message string, lists interface{}, totalCount, page int, pageSize int) *ApiJsonResult { 60 | totalPageCount := 0 61 | if pageSize > 0 { 62 | totalPageCount = int(math.Ceil(float64(totalCount/pageSize))) 63 | } 64 | return &ApiJsonResult{ 65 | Code: code, 66 | Data: ApiPagingJsonResult { 67 | TotalCount: totalCount, 68 | TotalPageCount: totalPageCount, 69 | Page: page, 70 | PageSize: pageSize, 71 | Lists: lists, 72 | }, 73 | Message: message, 74 | } 75 | } -------------------------------------------------------------------------------- /resources/themes/default/simple/get.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "default/simple/get.tmpl" }} 2 | 3 | 4 | 5 | 6 | {{ .title }} 7 | 13 | 14 | 15 |

{{ .content }}

16 |

时间: {{ .nowDate }}

17 |

主题: default

18 |

模板: default/simple/get.tmpl

19 | 20 | 21 | {{end}}} -------------------------------------------------------------------------------- /resources/themes/default_mobile/simple/get.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "default_mobile/simple/get.tmpl" }} 2 | 3 | 4 | 5 | 6 | {{ .title }} 7 | 14 | 15 | 16 |

{{ .content }}

17 |

时间: {{ .nowDate }}

18 |

主题: mobile

19 |

模板: default_mobile/simple/get.tmpl

20 | 21 | 22 | {{end}}} -------------------------------------------------------------------------------- /routers/api/v1/demo/demo_user_struct.go: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | type DemoUser struct { 4 | User string `form:"user" json:"user" xml:"user" binding:"required"` 5 | Pass string `form:"pass" json:"pass" xml:"pass" binding:"required"` 6 | } -------------------------------------------------------------------------------- /routers/api/v1/demo/gorm.go: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import ( 4 | "gapp/models" 5 | "gapp/pkg/util" 6 | "github.com/gin-gonic/gin" 7 | "net/http" 8 | "strconv" 9 | ) 10 | 11 | type GormDemoUser struct { 12 | ID int `json:"id"` 13 | User string `form:"user" json:"user" xml:"user" binding:"required"` 14 | Pass string `form:"pass" json:"pass" xml:"user" binding:"required"` 15 | } 16 | 17 | 18 | 19 | func (g GormDemoUser) TableName() string { 20 | return "demo_user" 21 | } 22 | 23 | // UserAdd gorm新增操作 24 | // 25 | // curl -X POST "http://localhost:8080/api/v1/demo/gorm/user" -d "user=test&pass=123456" 26 | // {"code":"200","message":"success","data":{"id":5,"user":"test","pass":"123456"}} 27 | func UserAdd(c *gin.Context) { 28 | var gormDemoUser GormDemoUser 29 | c.ShouldBind(&gormDemoUser) 30 | // 保存数据到数据库 31 | models.DB.Create(&gormDemoUser) 32 | c.JSON(http.StatusOK, util.NewApiJsonResult("200", "success").Simple(gormDemoUser)) 33 | } 34 | 35 | // UserDel删除记录 36 | // 37 | // curl -X DELETE "http://localhost:8080/api/v1/demo/gorm/user/1" 38 | // {"code":"200","message":"success","data":1}% 39 | func UserDel(c *gin.Context) { 40 | var gormDemoUser GormDemoUser 41 | id := c.Param("userId") 42 | models.DB.Where("id = ?", id).First(&gormDemoUser) 43 | if gormDemoUser.ID < 1{ 44 | c.JSON(http.StatusOK, util.NewApiJsonResult("404", "not found").Simple(nil)) 45 | return 46 | } 47 | gormDemoUser.ID, _ = strconv.Atoi(id) 48 | isDel := models.DB.Delete(gormDemoUser).RowsAffected 49 | if isDel < 1 { 50 | c.JSON(http.StatusOK, util.NewApiJsonResult("200", "fail").Simple(isDel)) 51 | } 52 | c.JSON(http.StatusOK, util.NewApiJsonResult("200", "success").Simple(isDel)) 53 | } 54 | 55 | // UserUpdate 更新记录 56 | // 57 | // curl -X PUT "http://localhost:8080/api/v1/demo/gorm/user/1" -d "user=test.update&pass=123456.update" 58 | // {"code":"200","message":"success","data":{"id":1,"user":"test.update","pass":"123456.update"}} 59 | func UserUpdate(c *gin.Context) { 60 | var gormDemoUser, updateGormDemoUser GormDemoUser 61 | id := c.Param("userId") 62 | models.DB.Where("id = ?", id).First(&gormDemoUser) 63 | if gormDemoUser.ID < 1{ 64 | c.JSON(http.StatusOK, util.NewApiJsonResult("404", "not found").Simple(gormDemoUser)) 65 | return 66 | } 67 | c.ShouldBind(&updateGormDemoUser) 68 | models.DB.Model(&gormDemoUser).Update(updateGormDemoUser) 69 | c.JSON(http.StatusOK, util.NewApiJsonResult("200", "success").Simple(gormDemoUser)) 70 | } 71 | 72 | // UserQuery 分页查询数据 73 | // 74 | // curl -X GET "http://localhost:8080/api/v1/demo/gorm/user?page=1&pageSize=2" 75 | // {"code":"200","message":"success","data":{"totalCount":3,"totalPageCount":1,"page":1,"pageSize":2,"lists":[{"id":1,"user":"test","pass":"123456"},{"id":2,"user":"test","pass":"123456"}]}} 76 | func UserQuery(c *gin.Context) { 77 | var listGormDomeUser[] *GormDemoUser 78 | count :=0 79 | page, _ := strconv.Atoi(c.Query("page")) 80 | pageSize, _ := strconv.Atoi(c.Query("pageSize")) 81 | offset := (page-1)*pageSize 82 | models.DB.Model(&GormDemoUser{}).Where("id > 0").Count(&count) 83 | models.DB.Offset(offset).Limit(pageSize).Find(&listGormDomeUser) 84 | c.JSON(http.StatusOK, util.NewApiJsonResult("200", "success").Paging(listGormDomeUser, count, page, pageSize)) 85 | } 86 | 87 | // UserDetail gormc查询单挑记录 88 | // 89 | // curl -X GET "http://localhost:8080/api/v1/demo/gorm/user/1" 90 | // {"code":"200","message":"success","data":{"id":1,"user":"test","pass":"123456"}} 91 | func UserDetail(c *gin.Context) { 92 | var gormDemoUser GormDemoUser 93 | id := c.Param("userId") 94 | models.DB.Where("id = ?", id).First(&gormDemoUser) 95 | c.JSON(http.StatusOK, util.NewApiJsonResult("200", "success").Simple(gormDemoUser)) 96 | } -------------------------------------------------------------------------------- /routers/api/v1/demo/gorm_raw_sql.go: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import ( 4 | "gapp/models" 5 | "gapp/pkg/util" 6 | "github.com/gin-gonic/gin" 7 | "net/http" 8 | ) 9 | 10 | // DemoRawInsertSql 原生insert sql 插入数据 11 | // 12 | // curl -X POST "http://localhost:8080/api/v1/demo/gorm-raw-sql/user" 13 | // {"code":"200","message":"success","data":1}% 14 | func DemoRawInsertSql(c *gin.Context) { 15 | sql := "insert demo_user(`user`, `pass`) values(?, ?);" 16 | rows := models.DB.Exec(sql, "test.DemoRawInsertSql", "123456.DemoRawInsertSql").RowsAffected 17 | if rows < 1 { 18 | c.JSON(http.StatusOK,util.GetApiJsonResult("200", "fail", rows)) 19 | return 20 | } 21 | c.JSON(http.StatusOK,util.GetApiJsonResult("200", "success", rows)) 22 | } 23 | 24 | // DemoRawDeleteSql 原生 delete sql 删除数据 25 | // 26 | // curl -X DELETE "http://localhost:8080/api/v1/demo/gorm-raw-sql/user" 27 | // {"code":"200","message":"success","data":1} 28 | func DemoRawDeleteSql(c *gin.Context) { 29 | sql := "delete from demo_user where `user` = ? limit 1;" 30 | rows := models.DB.Exec(sql, "test.DemoRawInsertSql").RowsAffected 31 | if rows < 1 { 32 | c.JSON(http.StatusOK,util.GetApiJsonResult("200", "fail", rows)) 33 | return 34 | } 35 | c.JSON(http.StatusOK,util.GetApiJsonResult("200", "success", rows)) 36 | } 37 | 38 | // DemoRawUpdateSql 原生 update sql 更新数据 39 | // curl -X PUT "http://localhost:8080/api/v1/demo/gorm-raw-sql/user" 40 | // {"code":"200","message":"success","data":3} 41 | func DemoRawUpdateSql(c *gin.Context) { 42 | sql := "update demo_user set `user` = ? where `user` = ?" 43 | rows := models.DB.Exec(sql, "test.DemoRawUpdateSql", "test.DemoRawInsertSql").RowsAffected 44 | if rows < 1 { 45 | c.JSON(http.StatusOK,util.GetApiJsonResult("200", "fail", rows)) 46 | return 47 | } 48 | c.JSON(http.StatusOK,util.GetApiJsonResult("200", "success", rows)) 49 | } 50 | 51 | // DemoRawSelecOnetSql 原生 select sql 查询单条数据 52 | // curl -X GET "http://localhost:8080/api/v1/demo/gorm-raw-sql/user/first-detail" 53 | //{"code":"200","message":"success","data":{"id":2,"user":"test","pass":"123456"}} 54 | func DemoRawSelecOnetSql(c *gin.Context) { 55 | var gormDemoUser GormDemoUser 56 | sql := "select * from demo_user limit 1" 57 | models.DB.Raw(sql).Scan(&gormDemoUser) 58 | models.DB.Raw(sql).Scan(&gormDemoUser) 59 | c.JSON(http.StatusOK,util.GetApiJsonResult("200", "success", gormDemoUser)) 60 | } 61 | 62 | // DemoRawSelectAllSql 原生 select sql 查询多条数据 63 | // 64 | // curl -X GET "http://localhost:8080/api/v1/demo/gorm-raw-sql/user" 65 | // {"code":"200","message":"success","data":[{"id":0,"user":"","pass":""},{"id":0,"user":"","pass":""},{"id":0,"user":"","pass":""},{"id":0,"user":"","pass":""},{"id":0,"user":"","pass":""},{"id":0,"user":"","pass":""},{"id":0,"user":"","pass":""}]} 66 | func DemoRawSelectAllSql(c *gin.Context) { 67 | var ListsGormDemoUser[] *GormDemoUser 68 | var gormDemoUser GormDemoUser 69 | 70 | sql := "select * from demo_user limit 10" 71 | rows, err := models.DB.Raw(sql).Rows() 72 | if err != nil { 73 | c.JSON(http.StatusOK,util.GetApiJsonResult("400", "fail", err)) 74 | } 75 | defer rows.Close() 76 | for rows.Next() { 77 | rows.Scan(&gormDemoUser) 78 | ListsGormDemoUser = append(ListsGormDemoUser, &gormDemoUser) 79 | } 80 | c.JSON(http.StatusOK,util.GetApiJsonResult("200", "success", ListsGormDemoUser)) 81 | } -------------------------------------------------------------------------------- /routers/api/v1/demo/simple.go: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | ) 8 | 9 | // Get get请求示例 10 | // 11 | // curl -X GET "http://localhost:8080/api/v1/demo/simple/get?user=test&pass=123456" 12 | // 13 | // url: "/api/v1/demo/simple/get?user=test&pass=123456" 14 | // demoUser: demo.DemoUser{User:"test", Pass:"123456"} 15 | // query.user: "test" 16 | // query.pass: "123456" 17 | func Get(c *gin.Context) { 18 | var demoUser DemoUser 19 | c.ShouldBindQuery(&demoUser) 20 | str := ` 21 | url: %#v 22 | demoUser: %#v 23 | query.user: %#v 24 | query.pass: %#v 25 | 26 | ` 27 | str = fmt.Sprintf( 28 | str, 29 | c.Request.RequestURI, 30 | demoUser, 31 | c.Query("user"), 32 | c.Query("pass"), 33 | ) 34 | c.String(http.StatusOK, str) 35 | } 36 | 37 | // Post post请求示例 38 | // 39 | // curl -X POST "http://localhost:8080/api/v1/demo/simple/post" -d "user=test&pass=123456" 40 | // 41 | // url: "/api/v1/demo/simple/post" 42 | // demoUser: demo.DemoUser{User:"test", Pass:"123456"} 43 | // formData.user: "test" 44 | // formData.pass: "123456" 45 | func Post(c *gin.Context) { 46 | var demoUser DemoUser 47 | c.ShouldBind(&demoUser) 48 | str := ` 49 | url: %#v 50 | demoUser: %#v 51 | formData.user: %#v 52 | formData.pass: %#v 53 | 54 | ` 55 | str = fmt.Sprintf( 56 | str, 57 | c.Request.RequestURI, 58 | demoUser, 59 | c.PostForm("user"), 60 | c.PostForm("pass"), 61 | ) 62 | c.String(http.StatusOK, str) 63 | } 64 | 65 | // Put put请求示例 66 | // 67 | // curl -X PUT "http://localhost:8080/api/v1/demo/simple/put" -d "user=test&pass=123456" 68 | // url: "/api/v1/demo/simple/put" 69 | // demoUser: demo.DemoUser{User:"test", Pass:"123456"} 70 | // formData.user: "test" 71 | // formData.pass: "123456" 72 | func Put(c *gin.Context) { 73 | var demoUser DemoUser 74 | c.ShouldBind(&demoUser) 75 | str := ` 76 | url: %#v 77 | demoUser: %#v 78 | formData.user: %#v 79 | formData.pass: %#v 80 | 81 | ` 82 | str = fmt.Sprintf( 83 | str, 84 | c.Request.RequestURI, 85 | demoUser, 86 | c.PostForm("user"), 87 | c.PostForm("pass"), 88 | ) 89 | c.String(http.StatusOK, str) 90 | } 91 | 92 | // Delete delete请求示例 93 | // 94 | // curl -X DELETE "http://localhost:8080/api/v1/demo/simple/delete?user=test&pass=123456" 95 | // 96 | // url: "/api/v1/demo/simple/delete?user=test&pass=123456" 97 | // demoUser: demo.DemoUser{User:"test", Pass:"123456"} 98 | // formData.user: "test" 99 | // formData.pass: "123456" 100 | func Delete(c *gin.Context) { 101 | var demoUser DemoUser 102 | c.ShouldBind(&demoUser) 103 | str := ` 104 | url: %#v 105 | demoUser: %#v 106 | formData.user: %#v 107 | formData.pass: %#v 108 | 109 | ` 110 | str = fmt.Sprintf( 111 | str, 112 | c.Request.RequestURI, 113 | demoUser, 114 | c.Query("user"), 115 | c.Query("pass"), 116 | ) 117 | c.String(http.StatusOK, str) 118 | } 119 | -------------------------------------------------------------------------------- /routers/api/v1/demo/validator.go: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import ( 4 | "gapp/pkg/util" 5 | "github.com/gin-gonic/gin" 6 | "github.com/asaskevich/govalidator" 7 | "log" 8 | "net/http" 9 | ) 10 | 11 | type ValidatorRequest struct { 12 | Id int64 `json:"id" valid:"required~id必须是整数"` 13 | Name string `json:"name" valid:"required~名字不能为空,runelength(1|10)~名字在1-10个字符之间"` 14 | } 15 | 16 | // curl -X POST "http://localhost:8080/api/v1/demo/validator/post" -d "id=1&name=\"123456\"" 17 | /* 18 | curl -X POST \ 19 | http://localhost:8080/api/v1/demo/validator/post \ 20 | -H 'Accept: application/json' \ 21 | -H 'Content-Type: application/json' \ 22 | -H 'cache-control: no-cache' \ 23 | -d '{ 24 | "id": 1, 25 | "name": "11" 26 | }' 27 | 28 | curl -X POST \ 29 | http://localhost:8080/api/v1/demo/validator/post \ 30 | -H 'Accept: application/json' \ 31 | -H 'Content-Type: application/json' \ 32 | -H 'cache-control: no-cache' \ 33 | -d '{ 34 | "id": "123", 35 | "name": "11" 36 | }' 37 | */ 38 | func Validator(c *gin.Context) { 39 | var r ValidatorRequest 40 | c.ShouldBind(&r) 41 | log.Printf("failed to create rotatelogs: %#v", r) 42 | // 验证数据 43 | if _, err := govalidator.ValidateStruct(&r); err != nil { 44 | errMap := govalidator.ErrorsByField(err) 45 | c.JSON(http.StatusOK, util.GetApiJsonResult("422", "govalidator fail", errMap)) 46 | return 47 | } 48 | c.JSON(http.StatusOK, util.GetApiJsonResult("200", "success", nil)) 49 | } -------------------------------------------------------------------------------- /routers/api_router.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "gapp/routers/api/v1/demo" 6 | ) 7 | 8 | // ApiV1 接口v1版本路由 9 | func ApiV1(engine *gin.Engine) { 10 | g := engine.Group("/api/v1") 11 | { 12 | // grom 模型操作示例 13 | g.POST("/demo/gorm/user", demo.UserAdd) 14 | g.GET("/demo/gorm/user", demo.UserQuery) 15 | g.GET("/demo/gorm/user/:userId", demo.UserDetail) 16 | g.DELETE("/demo/gorm/user/:userId", demo.UserDel) 17 | g.PUT("/demo/gorm/user/:userId", demo.UserUpdate) 18 | 19 | // grom 原生sql操作示例 20 | g.POST("/demo/gorm-raw-sql/user", demo.DemoRawInsertSql) 21 | g.GET("/demo/gorm-raw-sql/user/first-detail", demo.DemoRawSelecOnetSql) 22 | g.GET("/demo/gorm-raw-sql/user", demo.DemoRawSelectAllSql) 23 | g.DELETE("/demo/gorm-raw-sql/user", demo.DemoRawDeleteSql) 24 | g.PUT("/demo/gorm-raw-sql/user", demo.DemoRawUpdateSql) 25 | 26 | // 简单 get post put delete请求示例 27 | g.GET("/demo/simple/get", demo.Get) 28 | g.POST("/demo/simple/post", demo.Post) 29 | g.PUT("/demo/simple/put", demo.Put) 30 | g.DELETE("/demo/simple/delete", demo.Delete) 31 | 32 | // 简单验证示例 33 | g.POST("/demo/validator/post", demo.Validator) 34 | } 35 | } 36 | 37 | // ApiV2 接口v2版本路由 38 | func ApiV2(engine *gin.Engine) { 39 | //g := engine.Group("/api/v2") 40 | //{ 41 | // g.GET("/demo/json") 42 | //} 43 | } 44 | 45 | // ApiV3 接口v3版本路由 46 | func ApiV3(engine *gin.Engine) { 47 | //g := engine.Group("/api/v3") 48 | //{ 49 | // g.GET("/demo/json") 50 | //} 51 | } -------------------------------------------------------------------------------- /routers/app/v1/demo/simple_html.go: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | // Get 简单请求显示不同主题的模板 10 | // http://localhost:8080/app/v1/demo/simple-html?theme=default_mobile 11 | func Get(c *gin.Context) { 12 | data := gin.H{ 13 | "title": "gapp演示", 14 | "nowDate": time.Now().Format("2006-03-01 00:00:00"), 15 | "content": "GET请求演示", 16 | } 17 | theme := c.Query("theme") 18 | if theme == "" { 19 | theme = "default" 20 | } 21 | c.HTML(http.StatusOK, theme + "/simple/get.tmpl", data) 22 | } -------------------------------------------------------------------------------- /routers/app_router.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "gapp/routers/app/v1/demo" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | // AppV1 应用v1版本路由 9 | func AppV1(engine *gin.Engine) { 10 | g := engine.Group("/app/v1") 11 | { 12 | g.GET("/demo/simple-html", demo.Get) 13 | } 14 | } 15 | 16 | // AppV2 应用v2版本路由 17 | func AppV2(engine *gin.Engine) { 18 | //g := engine.Group("/app/v2") 19 | //{ 20 | // g.GET("/demo/json") 21 | //} 22 | } 23 | 24 | // AppV3 应用v3版本路由 25 | func AppV3(engine *gin.Engine) { 26 | //g := engine.Group("/app/v3") 27 | //{ 28 | // g.GET("/demo/json") 29 | //} 30 | } -------------------------------------------------------------------------------- /routers/router.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | 6 | 7 | // InitRouter 初始化路由 8 | func InitRouter(engine *gin.Engine) *gin.Engine { 9 | // api 接口路由配置 10 | ApiV1(engine) 11 | ApiV2(engine) 12 | ApiV3(engine) 13 | 14 | // app 应用路由配置 15 | AppV1(engine) 16 | AppV2(engine) 17 | AppV3(engine) 18 | return engine 19 | } 20 | -------------------------------------------------------------------------------- /var/log/.gitignore: -------------------------------------------------------------------------------- 1 | *.log --------------------------------------------------------------------------------