[ADD]项目初始化,包括gorm、gin、jwt以及swagger

This commit is contained in:
hehui 2019-08-03 00:22:47 +08:00
commit ca47c25386
23 changed files with 1025 additions and 0 deletions

18
.gitignore vendored Normal file
View File

@ -0,0 +1,18 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
conf/app.ini
.idea/*
main

10
README.md Normal file
View File

@ -0,0 +1,10 @@
# game_demo
```bush
game_demo/
├── conf # 用于存储配置文件
├── middleware # 应用中间件
├── models # 应用数据库模型
├── pkg # 第三方包
├── routers # 路由逻辑处理
└── runtime # 应用运行时数据
```

55
api/auth.go Normal file
View File

@ -0,0 +1,55 @@
package api
import (
"game_demo/models"
"game_demo/pkg/e"
"game_demo/pkg/logging"
"game_demo/pkg/util"
"github.com/astaxie/beego/validation"
"github.com/gin-gonic/gin"
"net/http"
)
type auth struct {
Username string `valid:"Required; MaxSize(50)"`
Password string `valid:"Required; MaxSize(50)"`
}
func GetAuth(c *gin.Context) {
username := c.Query("username")
password := c.Query("password")
valid := validation.Validation{}
a := auth{Username : username, Password : password}
ok, err := valid.Valid(&a)
logging.Info(err)
data := make(map[string]interface{})
code := e.INVALID_PARAMS
if ok {
isExist := models.CheckAuth(username, password)
if isExist {
token, err := util.GenerateToken(username, password)
if err != nil {
code = e.ERROR_AUTH_TOKEN
} else {
data["token"] = token
code = e.SUCCESS
}
} else {
code = e.ERROR_AUTH
}
} else {
for _, err := range valid.Errors {
logging.Info(err.Key, err.Message)
}
}
c.JSON(http.StatusOK, gin.H{
"code": code,
"msg" : e.GetMsg(code),
"data" : data,
})
}

138
api/v1/game.go Normal file
View File

@ -0,0 +1,138 @@
package v1
import (
"game_demo/models"
"game_demo/pkg/e"
"game_demo/pkg/logging"
"game_demo/pkg/setting"
"game_demo/pkg/util"
"github.com/Unknwon/com"
"github.com/astaxie/beego/validation"
"github.com/gin-gonic/gin"
"io/ioutil"
"net/http"
)
// @Summary 获取多个游戏
// @Description
// @Accept json
// @Produce json
// @Param Authorization header string true "auth by /auth"
// @Success 200 {string} json "{"code" : 200, "data" : {}, msg: "ok"}"
// @Router /v1/games [get]
func GetGames(c *gin.Context) {
name := c.Query("name")
maps := make(map[string]interface{})
data := make(map[string]interface{})
if name != "" {
maps["name"] = name
}
code := e.SUCCESS
data["lists"] = models.GetGames(util.GetPage(c), setting.PageSize, maps)
data["total"] = models.GetGameTotal(maps)
c.JSON(http.StatusOK, gin.H{
"code": code,
"msg": e.GetMsg(code),
"data": data,
})
}
// 新增游戏
func AddGame(c *gin.Context) {
name := c.PostForm("name")
cnName := c.PostForm("cn_name")
logo := c.PostForm("logo")
valid := validation.Validation{}
valid.Required(name, "name").Message("名称不能为空")
valid.Required(cnName, "cn_name").Message("英文名称不能为空")
valid.Required(logo, "logo").Message("logo不能为空")
code := e.INVALID_PARAMS
logging.Info(ioutil.ReadAll(c.Request.Body))
logging.Info(valid)
if ! valid.HasErrors() {
if ! models.ExistGameByName(name) {
code = e.SUCCESS
models.AddGame(name, cnName, logo)
} else {
code = e.ERROR_EXIST_GAME
}
}
c.JSON(http.StatusOK, gin.H{
"code" : code,
"msg" : e.GetMsg(code),
"data" : make(map[string]string),
})
}
// 修改游戏
func EditGame(c *gin.Context) {
id := com.StrTo(c.Param("id")).MustInt()
name := c.PostForm("name")
cnName := c.PostForm("cn_name")
logo := c.PostForm("logo")
valid := validation.Validation{}
valid.Min(id, 1, "id").Message("ID必须大于0")
code := e.INVALID_PARAMS
if ! valid.HasErrors() {
code = e.SUCCESS
if models.ExistGameById(id) {
data := make(map[string]interface{})
if name != "" {
data["name"] = name
}
if cnName != "" {
data["cn_name"] = cnName
}
if logo != "" {
data["logo"] = logo
}
models.EditGame(id, data)
} else {
code = e.ERROR_NOT_EXIST_GAME
}
}
c.JSON(http.StatusOK, gin.H{
"code" : code,
"msg" : e.GetMsg(code),
"data" : make(map[string]string),
})
}
// 删除游戏
func DeleteGame(c *gin.Context) {
id := com.StrTo(c.Param("id")).MustInt()
valid := validation.Validation{}
valid.Min(id, 1, "id").Message("ID必须大于0")
code := e.INVALID_PARAMS
if ! valid.HasErrors() {
code = e.SUCCESS
if models.ExistGameById(id) {
models.DeleteGame(id)
} else {
code = e.ERROR_NOT_EXIST_GAME
}
}
c.JSON(http.StatusOK, gin.H{
"code" : code,
"msg" : e.GetMsg(code),
"data" : make(map[string]string),
})
}

106
docs/docs.go Normal file
View File

@ -0,0 +1,106 @@
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag at
// 2019-08-03 00:06:46.857848 +0800 CST m=+0.026554074
package docs
import (
"bytes"
"encoding/json"
"strings"
"github.com/alecthomas/template"
"github.com/swaggo/swag"
)
var doc = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{.Description}}",
"title": "{{.Title}}",
"termsOfService": "http://swagger.io/terms/",
"contact": {},
"license": {},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/v1/games": {
"get": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "获取多个游戏",
"parameters": [
{
"type": "string",
"description": "auth by /auth",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "{\"code\" : 200, \"data\" : {}, msg: \"ok\"}",
"schema": {
"type": "string"
}
}
}
}
}
}
}`
type swaggerInfo struct {
Version string
Host string
BasePath string
Schemes []string
Title string
Description string
}
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = swaggerInfo{
Version: "1.0",
Host: "",
BasePath: "/api",
Schemes: []string{},
Title: "GameDemo API",
Description: "This is a sample server Petstore server.",
}
type s struct{}
func (s *s) ReadDoc() string {
sInfo := SwaggerInfo
sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1)
t, err := template.New("swagger_info").Funcs(template.FuncMap{
"marshal": func(v interface{}) string {
a, _ := json.Marshal(v)
return string(a)
},
}).Parse(doc)
if err != nil {
return doc
}
var tpl bytes.Buffer
if err := t.Execute(&tpl, sInfo); err != nil {
return doc
}
return tpl.String()
}
func init() {
swag.Register(swag.Name, &s{})
}

42
docs/swagger.json Normal file
View File

@ -0,0 +1,42 @@
{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.",
"title": "GameDemo API",
"termsOfService": "http://swagger.io/terms/",
"contact": {},
"license": {},
"version": "1.0"
},
"basePath": "/api",
"paths": {
"/v1/games": {
"get": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "获取多个游戏",
"parameters": [
{
"type": "string",
"description": "auth by /auth",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "{\"code\" : 200, \"data\" : {}, msg: \"ok\"}",
"schema": {
"type": "string"
}
}
}
}
}
}
}

28
docs/swagger.yaml Normal file
View File

@ -0,0 +1,28 @@
basePath: /api
info:
contact: {}
description: This is a sample server Petstore server.
license: {}
termsOfService: http://swagger.io/terms/
title: GameDemo API
version: "1.0"
paths:
/v1/games:
get:
consumes:
- application/json
parameters:
- description: auth by /auth
in: header
name: Authorization
required: true
type: string
produces:
- application/json
responses:
"200":
description: '{"code" : 200, "data" : {}, msg: "ok"}'
schema:
type: string
summary: 获取多个游戏
swagger: "2.0"

56
main.go Normal file
View File

@ -0,0 +1,56 @@
package main
import (
_ "./docs"
"context"
"fmt"
"game_demo/pkg/setting"
"game_demo/routers"
ginSwagger "github.com/swaggo/gin-swagger"
"github.com/swaggo/gin-swagger/swaggerFiles"
"log"
"net/http"
"os"
"os/signal"
"time"
)
// @title GameDemo API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @BasePath /api
func main() {
router := routers.InitRouter()
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
s := &http.Server{
Addr: fmt.Sprintf(":%d", setting.HTTPPort),
Handler: router,
ReadTimeout: setting.ReadTimeout,
WriteTimeout: setting.WriteTimeout,
MaxHeaderBytes: 1 << 20,
}
go func() {
if err := s.ListenAndServe(); err != nil {
log.Printf("Listen: %s\n", err)
}
}()
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt)
<- quit
log.Printf("Shutdown Server")
ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
defer cancel()
if err := s.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
log.Printf("Server exiting")
}

42
middleware/jwt/jwt.go Normal file
View File

@ -0,0 +1,42 @@
package jwt
import (
"game_demo/pkg/e"
"game_demo/pkg/util"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func JWT() gin.HandlerFunc {
return func(c *gin.Context) {
var code int
var data interface{}
code = e.SUCCESS
token := c.Request.Header.Get("Authorization")
if token == "" {
code = e.INVALID_PARAMS
} else {
claims, err := util.ParseToken(token)
if err != nil {
code = e.ERROR_AUTH_CHECK_TOKEN_FAIL
} else if time.Now().Unix() > claims.ExpiresAt {
code = e.ERROR_AUTH_CHECK_TOKEN_TIMEOUT
}
}
if code != e.SUCCESS {
c.JSON(http.StatusUnauthorized, gin.H{
"code" : code,
"msg" : e.GetMsg(code),
"data" : data,
})
c.Abort()
return
}
c.Next()
}
}

16
models/auth.go Normal file
View File

@ -0,0 +1,16 @@
package models
type Auth struct {
ID int `gorm:"primary_key" json:"id"`
Username string `json:"username"`
Password string `json:"password"`
}
func CheckAuth(username, password string) bool {
var auth Auth
db.Select("id").Where(Auth{Username : username, Password : password}).First(&auth)
if auth.ID > 0 {
return true
}
return false
}

60
models/game.go Normal file
View File

@ -0,0 +1,60 @@
package models
type Game struct {
Model
Name string `json:"name"`
CnName string `json:"cn_name"`
Logo string `json:"logo"`
}
func GetGames(pageNum int, pageSize int, maps interface{}) (games []Game) {
db.Where(maps).Offset(pageNum).Limit(pageSize).Find(&games)
return
}
func GetGameTotal(maps interface{}) (count int) {
db.Model(&Game{}).Where(maps).Count(&count)
return
}
func ExistGameByName(name string) bool {
var game Game
db.Select("id").Where("name = ?", name).First(&game)
if game.ID > 0 {
return true
}
return false
}
func ExistGameById(id int) bool {
var game Game
db.Select("id").Where("id = ?", id).First(&game)
if game.ID > 0 {
return true
}
return false
}
func AddGame(name string, cnName string, logo string) bool {
db.Create(&Game {
Name: name,
CnName: cnName,
Logo: logo,
})
return true
}
func EditGame(id int, data interface{}) bool {
db.Model(&Game{}).Where("id = ?", id).Update(data)
return true
}
func DeleteGame(id int) bool {
db.Where("id = ?", id).Delete(&Game{})
return true
}

70
models/models.go Normal file
View File

@ -0,0 +1,70 @@
package models
import (
"fmt"
"game_demo/pkg/logging"
"game_demo/pkg/setting"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
"time"
)
var db = InitDbConnect()
type Model struct {
ID int `gorm:"primary_key" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `sql:"index" json:"deleted_at"`
}
func init() {
db.LogMode(true)
db.SingularTable(true)
db.DB().SetMaxIdleConns(10)
db.DB().SetMaxOpenConns(100)
DbMigrate()
//CloseDB()
}
func InitDbConnect() *gorm.DB {
var (
err error
dbType, dbName, user, password, host, port, tablePrefix string
)
sec, err := setting.Cfg.GetSection("database")
if err != nil {
logging.Info(2, "Fail to get section 'database': %v", err)
}
dbType = sec.Key("TYPE").String()
dbName = sec.Key("NAME").String()
user = sec.Key("USER").String()
password = sec.Key("PASSWORD").String()
host = sec.Key("HOST").String()
port = sec.Key("PORT").String()
tablePrefix = sec.Key("TABLE_PREFIX").String()
db, err := gorm.Open(dbType, fmt.Sprintf("host=%s user=%s dbname=%s port=%s sslmode=disable password=%s", host, user, dbName, port, password))
if err != nil {
logging.Info(err)
}
gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string {
return tablePrefix + defaultTableName
}
return db
}
func DbMigrate() {
db.AutoMigrate(&Game{}, &Auth{})
}
func CloseDB() {
defer db.Close()
}

19
pkg/e/code.go Normal file
View File

@ -0,0 +1,19 @@
package e
const (
SUCCESS = 200
ERROR = 500
INVALID_PARAMS = 400
ERROR_EXIST_GAME = 10001
ERROR_EXIST_TEAM = 10002
ERROR_NOT_EXIST_GAME = 10003
ERROR_NOT_EXIST_TEAM = 10004
ERROR_NOT_EXIST_LEAGUE = 10005
ERROR_NOT_EXIST_SERIES = 10006
ERROR_AUTH_CHECK_TOKEN_FAIL = 20001
ERROR_AUTH_CHECK_TOKEN_TIMEOUT = 20002
ERROR_AUTH_TOKEN = 20003
ERROR_AUTH = 20004
)

27
pkg/e/msg.go Normal file
View File

@ -0,0 +1,27 @@
package e
var MsgFlags = map[int]string {
SUCCESS : "ok",
ERROR : "fail",
INVALID_PARAMS : "请求参数错误",
ERROR_EXIST_GAME : "已存在该游戏",
ERROR_EXIST_TEAM : "已存在该队伍",
ERROR_NOT_EXIST_GAME : "该游戏不存在",
ERROR_NOT_EXIST_TEAM : "该队伍不存在",
ERROR_NOT_EXIST_LEAGUE : "该联赛不存在",
ERROR_NOT_EXIST_SERIES : "该系列赛不存在",
ERROR_AUTH_CHECK_TOKEN_FAIL : "Token鉴权失败",
ERROR_AUTH_CHECK_TOKEN_TIMEOUT : "Token已超时",
ERROR_AUTH_TOKEN : "Token生成失败",
ERROR_AUTH : "Token错误",
}
func GetMsg(code int) string {
msg, ok := MsgFlags[code]
if ok {
return msg
}
return MsgFlags[ERROR]
}

51
pkg/logging/file.go Normal file
View File

@ -0,0 +1,51 @@
package logging
import (
"fmt"
"log"
"os"
"time"
)
var (
LogSavePath = "runtime/logs/"
LogSaveName = "log"
LogFileExt = "log"
TimeFormat = "20060102"
)
func getLogFilePath() string {
return fmt.Sprintf("%s", LogSavePath)
}
func getLogFileFullPath() string {
prefixPath := getLogFilePath()
suffixPath := fmt.Sprintf("%s%s.%s", LogSaveName, time.Now().Format(TimeFormat), LogFileExt)
return fmt.Sprintf("%s%s", prefixPath, suffixPath)
}
func openLogFile(filePath string) *os.File {
_, err := os.Stat(filePath)
switch {
case os.IsNotExist(err):
mkDir()
case os.IsPermission(err):
log.Fatalf("Permission :%v", err)
}
handle, err := os.OpenFile(filePath, os.O_APPEND | os.O_CREATE | os.O_WRONLY, 0644)
if err != nil {
log.Fatalf("Fail to OpenFile :%v", err)
}
return handle
}
func mkDir() {
dir, _ := os.Getwd()
err := os.MkdirAll(dir + "/" + getLogFilePath(), os.ModePerm)
if err != nil {
panic(err)
}
}

73
pkg/logging/log.go Normal file
View File

@ -0,0 +1,73 @@
package logging
import (
"fmt"
"log"
"os"
"path/filepath"
"runtime"
)
type Level int
var (
F *os.File
DefaultPrefix = ""
DefaultCallerDepth = 2
logger *log.Logger
logPrefix = ""
levelFlags = []string{"DEBUG", "INFO", "WARN", "ERROR", "FATAL"}
)
const (
DEBUG Level = iota
INFO
WARNING
ERROR
FATAL
)
func init() {
filePath := getLogFileFullPath()
F = openLogFile(filePath)
logger = log.New(F, DefaultPrefix, log.LstdFlags)
}
func Debug(v ...interface{}) {
setPrefix(DEBUG)
logger.Println(v)
}
func Info(v ...interface{}) {
setPrefix(INFO)
logger.Println(v)
}
func Warn(v ...interface{}) {
setPrefix(WARNING)
logger.Println(v)
}
func Error(v ...interface{}) {
setPrefix(ERROR)
logger.Println(v)
}
func Fatal(v ...interface{}) {
setPrefix(FATAL)
logger.Fatalln(v)
}
func setPrefix(level Level) {
_, file, line, ok := runtime.Caller(DefaultCallerDepth)
if ok {
logPrefix = fmt.Sprintf("[%s][%s:%d]", levelFlags[level], filepath.Base(file), line)
} else {
logPrefix = fmt.Sprintf("[%s]", levelFlags[level])
}
logger.SetPrefix(logPrefix)
}

59
pkg/setting/setting.go Normal file
View File

@ -0,0 +1,59 @@
package setting
import (
"gopkg.in/ini.v1"
"log"
"time"
)
var (
Cfg *ini.File
RunMode string
HTTPPort int
ReadTimeout time.Duration
WriteTimeout time.Duration
PageSize int
JwtSecret string
)
func init() {
var err error
Cfg, err = ini.Load("conf/app.ini")
if err != nil {
log.Fatal("Fail to parse 'conf/app.ini': %v", err)
}
LoadBase()
LoadServer()
LoadApp()
}
func LoadBase() {
RunMode = Cfg.Section("").Key("RUN_MODE").MustString("debug")
}
func LoadServer() {
sec, err := Cfg.GetSection("server")
if err != nil {
log.Fatalf("Fail to get section 'server': %v", err)
}
RunMode = Cfg.Section("").Key("RUN_MODE").MustString("debug")
HTTPPort = sec.Key("HTTP_PORT").MustInt(8000)
ReadTimeout = time.Duration(sec.Key("READ_TIMEOUT").MustInt(60)) * time.Second
WriteTimeout = time.Duration(sec.Key("WRITE_TIMEOUT").MustInt(60)) * time.Second
}
func LoadApp() {
sec, err := Cfg.GetSection("app")
if err != nil {
log.Fatalf("Fail to get section 'app': %v", err)
}
JwtSecret = sec.Key("JWT_SECRET").MustString("!@)*#)!@U#@*!@!)")
PageSize = sec.Key("PAGE_SIZE").MustInt(10)
}

48
pkg/util/jwt.go Normal file
View File

@ -0,0 +1,48 @@
package util
import (
"game_demo/pkg/setting"
"github.com/dgrijalva/jwt-go"
"time"
)
var jwtSecret = []byte(setting.JwtSecret)
type Claims struct {
Username string `json:"username"`
Password string `json:"password"`
jwt.StandardClaims
}
func GenerateToken(username, password string) (string, error) {
nowTime := time.Now()
expireTime := nowTime.Add(3 * time.Hour)
claims := Claims{
Username: username,
Password: password,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expireTime.Unix(),
Issuer : "gin-game-demo",
},
}
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
token, err := tokenClaims.SignedString(jwtSecret)
return token, err
}
func ParseToken(token string) (*Claims, error) {
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if tokenClaims != nil {
if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
return claims, nil
}
}
return nil, err
}

18
pkg/util/pagination.go Normal file
View File

@ -0,0 +1,18 @@
package util
import (
"game_demo/pkg/setting"
"github.com/Unknwon/com"
"github.com/gin-gonic/gin"
)
func GetPage(c *gin.Context) int {
result := 0
page, _ := com.StrTo(c.Query("page")).Int()
if page > 0 {
result = (page - 1) * setting.PageSize
}
return result
}

25
routers/router.go Normal file
View File

@ -0,0 +1,25 @@
package routers
import (
"game_demo/api"
"game_demo/pkg/setting"
"game_demo/routers/v1"
"github.com/gin-gonic/gin"
)
func InitRouter() *gin.Engine {
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
gin.SetMode(setting.RunMode)
r.GET("/auth", api.GetAuth)
// 加载模块的路由
v1.GameRouter(r)
return r
}

23
routers/v1/game.go Normal file
View File

@ -0,0 +1,23 @@
package v1
import (
"game_demo/api/v1"
"game_demo/middleware/jwt"
"github.com/gin-gonic/gin"
)
func GameRouter(r *gin.Engine) {
apiv1 := r.Group("../api/v1")
apiv1.Use(jwt.JWT())
{
// 获取游戏列表
apiv1.GET("/games", v1.GetGames)
// 新建游戏
apiv1.POST("/games", v1.AddGame)
// 更新指定游戏
apiv1.PUT("/games/:id", v1.EditGame)
// 删除指定游戏
apiv1.DELETE("/games/:id", v1.DeleteGame)
}
}

View File

@ -0,0 +1,40 @@
[INFO][auth.go:26]2019/08/02 19:59:10 [<nil>]
[INFO][auth.go:26]2019/08/02 19:59:27 [<nil>]
[INFO][auth.go:26]2019/08/02 19:59:29 [<nil>]
[INFO][auth.go:26]2019/08/02 19:59:38 [<nil>]
[INFO][auth.go:46]2019/08/02 19:59:38 [Password.Required Can not be empty]
[INFO][auth.go:26]2019/08/02 19:59:45 [<nil>]
[INFO][auth.go:46]2019/08/02 19:59:45 [Username.Required Can not be empty]
[INFO][auth.go:46]2019/08/02 19:59:45 [Password.Required Can not be empty]
[INFO][models.go:54]2019/08/02 20:01:16 [dial tcp 127.0.0.1:543: connect: connection refused]
[INFO][main.go:36]2019/08/02 20:18:21 [Shutdown Server]
[INFO][main.go:44]2019/08/02 20:18:21 [Server exiting]
[INFO][main.go:36]2019/08/02 20:22:44 [Shutdown Server]
[INFO][main.go:44]2019/08/02 20:22:44 [Server exiting]
[INFO][main.go:28]2019/08/02 20:22:44 [Listen: %s
http: Server closed]
[INFO][main.go:36]2019/08/02 20:22:53 [Shutdown Server]
[INFO][main.go:44]2019/08/02 20:22:53 [Server exiting]
[INFO][main.go:28]2019/08/02 20:22:53 [Listen: %s
http: Server closed]
[INFO][main.go:36]2019/08/02 20:23:35 [Shutdown Server]
[INFO][main.go:28]2019/08/02 20:23:35 [Listen: %s
http: Server closed]
[INFO][main.go:44]2019/08/02 20:23:35 [Server exiting]
[INFO][main.go:36]2019/08/02 20:23:38 [Shutdown Server]
[INFO][main.go:44]2019/08/02 20:23:38 [Server exiting]
[INFO][main.go:28]2019/08/02 20:23:38 [Listen: %s
http: Server closed]
[INFO][main.go:36]2019/08/02 20:28:25 [Shutdown Server]
[INFO][main.go:44]2019/08/02 20:28:25 [Server exiting]
[INFO][main.go:28]2019/08/02 20:28:25 [Listen: %s
http: Server closed]
[INFO][main.go:36]2019/08/02 20:28:31 [Shutdown Server]
[INFO][main.go:44]2019/08/02 20:28:31 [Server exiting]
[INFO][main.go:36]2019/08/02 20:28:49 [Shutdown Server]
[INFO][main.go:44]2019/08/02 20:28:49 [Server exiting]
[INFO][main.go:37]2019/08/02 20:28:59 [Shutdown Server]
[INFO][main.go:45]2019/08/02 20:28:59 [Server exiting]
[INFO][auth.go:26]2019/08/02 23:42:23 [<nil>]
[INFO][auth.go:46]2019/08/02 23:42:23 [Username.Required Can not be empty]
[INFO][auth.go:46]2019/08/02 23:42:23 [Password.Required Can not be empty]

View File

@ -0,0 +1 @@
[INFO][auth.go:26]2019/08/03 00:19:40 [<nil>]