diff --git a/.gitignore b/.gitignore index f1c181e..65ad3d2 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out + +conf/app.ini +.idea/* \ No newline at end of file diff --git a/conf/app.ini b/conf/app.ini new file mode 100644 index 0000000..09028b0 --- /dev/null +++ b/conf/app.ini @@ -0,0 +1,23 @@ +RUN_MODE = debug + +[app] +PAGE_SIZE = 10 +JWT_SECRET = 23347$040412 + +[server] +HTTP_PORT = 8000 +READ_TIMEOUT = 60 +WRITE_TIMEOUT = 60 + +[database] +TYPE = postgres +USER = postgre +PASSWORD = 123456 +HOST = 127.0.0.1 +PORT = 5432 +NAME = data_govern +TABLE_PREFIX = d_ + +[redis] +HOST = 127.0.0.1 +PORT = 6379 \ No newline at end of file diff --git a/conf/app.ini.local b/conf/app.ini.local new file mode 100644 index 0000000..4478ed6 --- /dev/null +++ b/conf/app.ini.local @@ -0,0 +1,19 @@ +RUN_MODE = debug + +[app] +PAGE_SIZE = 10 +JWT_SECRET = 23347$040412 + +[server] +HTTP_PORT = 8000 +READ_TIMEOUT = 60 +WRITE_TIMEOUT = 60 + +[database] +TYPE = postgres +USER = postgres +PASSWORD = 123456 +HOST = postgres +PORT = 5432 +NAME = dbname +TABLE_PREFIX = g_ \ No newline at end of file diff --git a/controller/api/auth.go b/controller/api/auth.go new file mode 100644 index 0000000..5cc256b --- /dev/null +++ b/controller/api/auth.go @@ -0,0 +1,7 @@ +package api + +import "github.com/gin-gonic/gin" + +func GetAuth(c *gin.Context) { + +} \ No newline at end of file diff --git a/controller/api/v1/data/meta_database.go b/controller/api/v1/data/meta_database.go new file mode 100644 index 0000000..0912983 --- /dev/null +++ b/controller/api/v1/data/meta_database.go @@ -0,0 +1,39 @@ +package data + +import ( + "github.com/gin-gonic/gin" + _ "github.com/viletyy/potato/models/data" + data2 "github.com/viletyy/potato/models/data" + "github.com/viletyy/potato/pkg/e" + "github.com/viletyy/potato/pkg/setting" + "github.com/viletyy/potato/pkg/util" + "net/http" +) + +// @Summary 数据源列表 +// @Tags meta_databases +// @Description +// @Accept json +// @Produce json +// @Success 200 {string} json "{"code" : 200, "data" : {}, "msg": "ok" }" +// @Router /v1/meta_databases [get] +func GetMetaDatabases(c *gin.Context) { + name := c.Query("name") + + maps := make(map[string]interface{}) + data := make(map[string]interface{}) + + if name != "" { + maps["name"] = name + } + + data["lists"] = data2.GetMetaDatabases(util.GetPage(c), setting.PageSize, maps) + data["total"] = data2.GetMetaDatabaseTotal(maps) + code := e.SUCCESS + + c.JSON(http.StatusOK, gin.H{ + "code": code, + "msg": e.GetMsg(code), + "data": data, + }) +} \ No newline at end of file diff --git a/controller/api/v1/data/meta_table.go b/controller/api/v1/data/meta_table.go new file mode 100644 index 0000000..22981f7 --- /dev/null +++ b/controller/api/v1/data/meta_table.go @@ -0,0 +1,34 @@ +package data + +import ( + "github.com/gin-gonic/gin" + "github.com/viletyy/potato/pkg/e" + "net/http" +) + +// @Summary 元数据列表 +// @Tags meta_tables +// @Description +// @Accept json +// @Produce json +// @Param id path int true "数据源 ID" +// @Success 200 {string} json "{"code" : 200, "data" : {}, "msg": "ok" }" +// @Router /v1/meta_databases/:id/meta_tables [get] +func GetMetaTables(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 + + c.JSON(http.StatusOK, gin.H{ + "code": code, + "msg": e.GetMsg(code), + "data": data, + }) +} \ No newline at end of file diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..ef86603 --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,76 @@ +// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT +// This file was generated by swaggo/swag at +// 2019-08-07 19:40:27.3805 +0800 CST m=+0.046403409 + +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}}", + "contact": {}, + "license": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": {} +}` + +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: "/", + Schemes: []string{}, + Title: "Potato Api", + Description: "This is a data_govern use golang", +} + +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{}) +} diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 0000000..dfb8318 --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,12 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is a data_govern use golang", + "title": "Potato Api", + "contact": {}, + "license": {}, + "version": "1.0" + }, + "basePath": "/", + "paths": {} +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..96e91f8 --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,9 @@ +basePath: / +info: + contact: {} + description: This is a data_govern use golang + license: {} + title: Potato Api + version: "1.0" +paths: {} +swagger: "2.0" diff --git a/main.go b/main.go new file mode 100644 index 0000000..f7b7c67 --- /dev/null +++ b/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "context" + "fmt" + "github.com/viletyy/potato/pkg/setting" + "github.com/viletyy/potato/pkg/util" + "github.com/viletyy/potato/routers" + "log" + "net/http" + "os" + "os/signal" + "time" +) + +// @title Potato Api +// @version 1.0 +// @description This is a data_govern use golang +// @BasePath /api + +func main() { + router := routers.InitRouter() + + // 数据库配置 + _ = util.InitDB() + defer util.CloseDB() + util.InitRedis() + defer util.CloseRedis() + + server := &http.Server{ + Addr: fmt.Sprintf(":%d", setting.HTTPPort), + Handler: router, + ReadTimeout: setting.ReadTimeout, + WriteTimeout: setting.WriteTimeout, + MaxHeaderBytes: 1 << 20, + } + + go func() { + if err := server.ListenAndServe(); err != nil { + log.Printf(fmt.Sprintf("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 := server.Shutdown(ctx); err != nil { + log.Fatal("Server Shutdown:", err) + } + + log.Printf("Server exiting") +} \ No newline at end of file diff --git a/middleware/cors/cors.go b/middleware/cors/cors.go new file mode 100644 index 0000000..8e1c51a --- /dev/null +++ b/middleware/cors/cors.go @@ -0,0 +1,19 @@ +package cors + +import "github.com/gin-gonic/gin" + +func CORSMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + c.Writer.Header().Set("Access-Control-Allow-Origin", "*") + c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") + 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") + c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE") + + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(204) + return + } + + c.Next() + } +} \ No newline at end of file diff --git a/middleware/jwt/jwt.go b/middleware/jwt/jwt.go new file mode 100644 index 0000000..cbeba03 --- /dev/null +++ b/middleware/jwt/jwt.go @@ -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() + } +} \ No newline at end of file diff --git a/models/data/meta_database.go b/models/data/meta_database.go new file mode 100644 index 0000000..86390f9 --- /dev/null +++ b/models/data/meta_database.go @@ -0,0 +1,23 @@ +package data + +import "github.com/viletyy/potato/pkg/util" + +type MetaDatabase struct { + util.Model + + Name string `json:"name"` + CnName string `json:"cn_name"` + Logo string `json:"logo"` +} + +func GetMetaDatabases(pageNum int, pageSize int, maps interface{}) (metaDatabases []MetaDatabase) { + util.DB.Where(maps).Offset(pageNum).Limit(pageSize).Find(&metaDatabases) + return +} + +func GetMetaDatabaseTotal(maps interface{}) (count int) { + util.DB.Model(&MetaDatabase{}).Where(maps).Count(&count) + + return +} + diff --git a/pkg/e/code.go b/pkg/e/code.go new file mode 100644 index 0000000..622b77b --- /dev/null +++ b/pkg/e/code.go @@ -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 +) \ No newline at end of file diff --git a/pkg/e/msg.go b/pkg/e/msg.go new file mode 100644 index 0000000..529c8f2 --- /dev/null +++ b/pkg/e/msg.go @@ -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] +} \ No newline at end of file diff --git a/pkg/logging/file.go b/pkg/logging/file.go new file mode 100644 index 0000000..484ed5e --- /dev/null +++ b/pkg/logging/file.go @@ -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 :%v1", err) + } + + handle, err := os.OpenFile(filePath, os.O_APPEND | os.O_CREATE | os.O_WRONLY, 0644) + if err != nil { + log.Fatalf("Fail to OpenFile :%v1", err) + } + + return handle +} + +func mkDir() { + dir, _ := os.Getwd() + err := os.MkdirAll(dir + "/" + getLogFilePath(), os.ModePerm) + if err != nil { + panic(err) + } +} diff --git a/pkg/logging/log.go b/pkg/logging/log.go new file mode 100644 index 0000000..23f3564 --- /dev/null +++ b/pkg/logging/log.go @@ -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) +} \ No newline at end of file diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go new file mode 100644 index 0000000..f3901f6 --- /dev/null +++ b/pkg/setting/setting.go @@ -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': %v1", 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': %v1", 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': %v1", err) + } + + JwtSecret = sec.Key("JWT_SECRET").MustString("!@)*#)!@U#@*!@!)") + PageSize = sec.Key("PAGE_SIZE").MustInt(10) +} \ No newline at end of file diff --git a/pkg/util/Instance.go b/pkg/util/Instance.go new file mode 100644 index 0000000..4e03afd --- /dev/null +++ b/pkg/util/Instance.go @@ -0,0 +1,89 @@ +package util + +import ( + "fmt" + "github.com/go-redis/redis" + "github.com/jinzhu/gorm" + _ "github.com/jinzhu/gorm/dialects/postgres" + "github.com/viletyy/potato/pkg/logging" + "github.com/viletyy/potato/pkg/setting" + "time" +) + +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"` +} + + +var( + DB *gorm.DB + Redis *redis.Client +) + +func InitDB() *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, fmt.Sprintf("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 + } + + DB.LogMode(true) + + DB.SingularTable(true) + DB.DB().SetMaxIdleConns(10) + DB.DB().SetMaxOpenConns(100) + + return DB +} + +func InitRedis() { + sec, err := setting.Cfg.GetSection("redis") + + if err != nil { + logging.Info(2, fmt.Sprintf("Fail to get section 'redis': %v", err)) + } + + Redis = redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:%s", + sec.Key("HOST").String(), + sec.Key("PORT").String(), + ), + }) + + if _, err := Redis.Ping().Result(); err != nil { + logging.Fatal("redis连接失败!", err) + } +} + +func CloseDB() { + DB.Close() +} + +func CloseRedis() { + Redis.Close() +} diff --git a/pkg/util/jwt.go b/pkg/util/jwt.go new file mode 100644 index 0000000..25a6536 --- /dev/null +++ b/pkg/util/jwt.go @@ -0,0 +1,48 @@ +package util + +import ( + "github.com/dgrijalva/jwt-go" + "github.com/viletyy/potato/pkg/setting" + "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 +} \ No newline at end of file diff --git a/pkg/util/pagination.go b/pkg/util/pagination.go new file mode 100644 index 0000000..d449582 --- /dev/null +++ b/pkg/util/pagination.go @@ -0,0 +1,18 @@ +package util + +import ( + "github.com/Unknwon/com" + "github.com/viletyy/potato/pkg/setting" + + "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 +} \ No newline at end of file diff --git a/routers/data.go b/routers/data.go new file mode 100644 index 0000000..b2b2234 --- /dev/null +++ b/routers/data.go @@ -0,0 +1,13 @@ +package routers + +import ( + "github.com/viletyy/potato/controller/api/v1/data" +) + +func V1InitDataRouter() { + metaDatabases := V1RouterGroup.Group("/meta_databases") + { + metaDatabases.GET("", data.GetMetaDatabases) + metaDatabases.GET(":id/meta_tables", data.GetMetaTables) + } +} diff --git a/routers/router.go b/routers/router.go new file mode 100644 index 0000000..a2511fd --- /dev/null +++ b/routers/router.go @@ -0,0 +1,39 @@ +package routers + +import ( + "github.com/gin-gonic/gin" + _ "github.com/swaggo/gin-swagger" + ginSwagger "github.com/swaggo/gin-swagger" + "github.com/swaggo/gin-swagger/swaggerFiles" + _ "github.com/swaggo/gin-swagger/swaggerFiles" + "github.com/viletyy/potato/controller/api" + _ "github.com/viletyy/potato/docs" + "github.com/viletyy/potato/middleware/cors" + "github.com/viletyy/potato/pkg/setting" +) + +var ( + Engine = gin.Default() + V1RouterGroup = Engine.Group("../api/v1") +) + +func InitRouter() *gin.Engine { + Engine.Use(gin.Logger()) + + Engine.Use(gin.Recovery()) + + gin.SetMode(setting.RunMode) + + Engine.Use(cors.CORSMiddleware()) + + Engine.GET("/auth", api.GetAuth) + Engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + + V1InitModule() + + return Engine +} + +func V1InitModule() { + V1InitDataRouter() +}