diff --git a/.gitignore b/.gitignore index f2dd955..3fd54aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,21 @@ +.idea/ +.DS_Store +bin/ +pkg/ + # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib +*.log # 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/ \ No newline at end of file diff --git a/README.md b/README.md index 1019159..c6bae09 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,64 @@ # lingye-gin #### 介绍 -{**以下是 Gitee 平台说明,您可以替换此简介** -Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台 -无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)} +GO + Gin #### 软件架构 -软件架构说明 +```text +# go版本:1.16.3 --> from go version +# goland版本:2020.2.4 --> from https://www.jetbrains.com/go/download/other.html +# bin 可执行文件执行路径 +# doc 文档、图片等资源 +# logs 日志目录 +# src 源码目录 + - config 配置目录 + - application_config.go 配置文件解析配置 + - common.go 全局变量 + - log_config.go 日志配置 + - middleware 中间件目录 + - gin_engine.go gin配置 + - gin_router.go 路由载入配置 + - pkg 第三方依赖包目录 + - rest + - urls.go 路由配置(绑定路径和处理器之间的关系) + - util + - util.go 工具类 + - application.yml 主配置文件 + - application-dev.yml 环境配置文件 + - main.go 应用入口 +# go.mod 依赖说明文件 +``` -#### 安装教程 +#### 配置Goland +```text +# File | Settings | Go +1、GOROOT +Add SDK... +Local...(这里选择你的Go安装的根路径,我的是"/usr/local/go") +2、Go Modules +- Enabel Go modules integration打勾 +- +3、Go设置代理 +- 查看go 的环境变量 在cmd中 输入go env设置GOPROXY代理: +go env -w GO111MODULE=on +go env -w GOPROXY=https://goproxy.cn,direct +- 设置GOPRIVATE来跳过私有库,比如常用的Gitee,中间使用逗号分隔: +go env -w GOPRIVATE=*.gitee.com +- 如果在运行go mod vendor时,提示Get https://sum.golang.org/lookup/xxxxxx: dial tcp 216.58.200.49:443: i/o timeout,则是因为Go 1.13设置了默认的GOSUMDB=sum.golang.org,这个网站是被墙了的,用于验证包的有效性,可以通过如下命令关闭: +go env -w GOSUMDB=off +- 可以设置 GOSUMDB="sum.golang.google.cn", 这个是专门为国内提供的sum 验证服务。 +go env -w GOSUMDB="sum.golang.google.cn" -1. xxxx -2. xxxx -3. xxxx +# Apply,OK +``` #### 使用说明 - -1. xxxx -2. xxxx -3. xxxx - -#### 参与贡献 - -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request - - -#### 特技 - -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +- 配置GOPATH + - Preferences | Go | GOPATH | Project GOPATH + - add /usr/local/gitee/lingye-go-base/src (这个路径以你本地的路径为准) + - Index entire GOPATH, 打勾 + +- 转到src下,执行命令: go mod tidy, 安装依赖包 +- 运行配置 +![avatar](./doc/images/build_config.png) \ No newline at end of file diff --git a/src/application-dev.yml b/src/application-dev.yml new file mode 100644 index 0000000..bf004a6 --- /dev/null +++ b/src/application-dev.yml @@ -0,0 +1,21 @@ +app: + mode: debug + name: LingYeGin + # MD5 SHA256 + secret: 878bb8ef8807b5ddbcbddbf909e5526262cf74a54fb523cee17e5b80a1b7510d + +server: + port: 8088 + api: + expiry: 120 + +log: + file: + path: ../logs + name: lingyeGin.log + level: debug + +redis: + addr: localhost:6379 + passwd: + database: 10 \ No newline at end of file diff --git a/src/application.yml b/src/application.yml new file mode 100644 index 0000000..a80a164 --- /dev/null +++ b/src/application.yml @@ -0,0 +1,2 @@ +app: + env: dev \ No newline at end of file diff --git a/src/config/application_config.go b/src/config/application_config.go new file mode 100644 index 0000000..2a22f2e --- /dev/null +++ b/src/config/application_config.go @@ -0,0 +1,96 @@ +package config + +import ( + "fmt" + "gopkg.in/yaml.v2" + "io/ioutil" + "log" + "os" + "strings" +) + +// 配置文件 +type AppConfig struct { + // 环境 + Env string `yaml:"env"` + // 运行模式 + Mode string `yaml:"mode"` + // 应用名称 + Name string `yaml:"name"` + // 加密盐 + Secret string `yaml:"secret"` +} +type ServerConfig struct { + // 服务端口 + Port int `yaml:"port"` + // API相关配置 + Api ServerApiConfig `yaml:"api"` +} +type ServerApiConfig struct { + // 过期时间 + Expiry string `yaml:"expiry"` +} +type LogConfig struct { + // 日志文件配置 + File LogFileConfig `yaml:"file"` + // 日志级别(debug, info, warn, error) + Level string `yaml:"level"` +} +type LogFileConfig struct { + // 日志路径 + Path string `yaml:"path"` + // 日志名称 + Name string `yaml:"name"` +} + +type RedisConfig struct { + // 连接地址 + Addr string `yaml:"addr"` + // 密码 + Passwd string `yaml:"passwd"` + // 库索引 + Database int `yaml:"database"` +} + +type ApplicationProperties struct { + // 应用配置 + App AppConfig `yaml:"app"` + // Gin服务配置 + Server ServerConfig `yaml:"server"` + // 日志配置 + Log LogConfig `yaml:"log"` + // redis配置 + Redis RedisConfig `yaml:"redis"` +} + +// 根据路径读取yaml文件 +func readYaml(path string) ApplicationProperties { + var result ApplicationProperties + data, err := ioutil.ReadFile(path) + if err != nil { + log.Fatalf("File reading error, application.yml not exist!") + } + err = yaml.Unmarshal(data, &result) + if err != nil { + log.Fatalf("cannot unmarshal data: %v", err) + } + return result +} + +// 根据环境配置取配置明细 +func (v *ApplicationProperties) Init() { + result := readYaml(fmt.Sprintf("%s/application.yml", GetCurrentPath())) + // 判断环境 + if strings.Compare(result.App.Env, "dev") == 0 { + AppProps = readYaml(fmt.Sprintf("%s/application-%s.yml", GetCurrentPath(), "dev")) + } +} + +// 获取当前路径,比如:d:/abc +func GetCurrentPath() string { + dir, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + return strings.Replace(dir, "\\", "/", -1) +} diff --git a/src/config/common.go b/src/config/common.go new file mode 100644 index 0000000..b4ffd87 --- /dev/null +++ b/src/config/common.go @@ -0,0 +1,4 @@ +package config + +// 应用配置 +var AppProps ApplicationProperties diff --git a/src/config/log_config.go b/src/config/log_config.go new file mode 100644 index 0000000..5382a80 --- /dev/null +++ b/src/config/log_config.go @@ -0,0 +1,118 @@ +package config + +import ( + "fmt" + "github.com/gin-gonic/gin" + rotatelogs "github.com/lestrrat-go/file-rotatelogs" + "github.com/rifflock/lfshook" + "github.com/sirupsen/logrus" + "os" + "path" + "strings" + "time" +) + +// 实例化 +var Logger = logrus.New() + +// 日志记录到文件 +func LoggerToFile() gin.HandlerFunc { + logFilePath := AppProps.Log.File.Path + logFileName := AppProps.Log.File.Name + // 日志文件 + fileName := path.Join(logFilePath, logFileName) + + //禁止logrus的输出 + src, err := os.OpenFile(os.DevNull, os.O_APPEND|os.O_WRONLY, os.ModeAppend) + if err != nil { + panic(fmt.Sprintf("err: %v", err)) + } + + // 设置输出 + Logger.Out = src + // 设置日志级别 + if strings.Compare(AppProps.Log.Level, "debug") == 0 { + Logger.SetLevel(logrus.DebugLevel) + } else if strings.Compare(AppProps.Log.Level, "info") == 0 { + Logger.SetLevel(logrus.InfoLevel) + } else if strings.Compare(AppProps.Log.Level, "warn") == 0 { + Logger.SetLevel(logrus.WarnLevel) + } else if strings.Compare(AppProps.Log.Level, "error") == 0 { + Logger.SetLevel(logrus.ErrorLevel) + } + + // 设置 rotatelogs + logWriter, err := rotatelogs.New( + // 分割后的文件名称 + fileName+".%Y%m%d.log", + // 生成软链,指向最新日志文件 + rotatelogs.WithLinkName(fileName), + // 设置最大保存时间(7天) + rotatelogs.WithMaxAge(7*24*time.Hour), + // 设置日志切割时间间隔(1天) + rotatelogs.WithRotationTime(24*time.Hour), + ) + + writeMap := lfshook.WriterMap{ + logrus.InfoLevel: logWriter, + logrus.FatalLevel: logWriter, + logrus.DebugLevel: logWriter, + logrus.WarnLevel: logWriter, + logrus.ErrorLevel: logWriter, + logrus.PanicLevel: logWriter, + } + + // 新增钩子 + Logger.AddHook(lfshook.NewHook(writeMap, &logrus.JSONFormatter{ + // 这个日期是真的牛皮, yyyy-MM-dd hh:mm:ss它不香吗 + TimestampFormat: "2006-01-02 15:04:05", + })) + + return func(c *gin.Context) { + // 开始时间 + startTime := time.Now() + // 处理请求 + c.Next() + // 结束时间 + endTime := time.Now() + // 执行时间 + latencyTime := endTime.Sub(startTime) + // 请求方式 + reqMethod := c.Request.Method + // 请求路由 + reqUri := c.Request.RequestURI + // 状态码 + statusCode := c.Writer.Status() + // 请求IP + clientIP := c.ClientIP() + // 日志格式 + Logger.WithFields(logrus.Fields{ + "status_code": statusCode, + "latency_time": latencyTime, + "client_ip": clientIP, + "req_method": reqMethod, + "req_uri": reqUri, + }).Info() + } +} + +// 日志记录到 MongoDB +func LoggerToMongo() gin.HandlerFunc { + return func(c *gin.Context) { + + } +} + +// 日志记录到 ES +func LoggerToES() gin.HandlerFunc { + return func(c *gin.Context) { + + } +} + +// 日志记录到 MQ +func LoggerToMQ() gin.HandlerFunc { + return func(c *gin.Context) { + + } +} diff --git a/src/config/redis_config.go b/src/config/redis_config.go new file mode 100644 index 0000000..e392338 --- /dev/null +++ b/src/config/redis_config.go @@ -0,0 +1,356 @@ +package config + +import ( + "fmt" + "github.com/go-redis/redis" + "math/rand" + "time" +) + +var redisdb *redis.Client +var RedisClient *redis.Client + +type RedisPool struct{} + +func (p RedisPool) Init() { + // 连接服务器 + redisdb = redis.NewClient(&redis.Options{ + Addr: AppProps.Redis.Addr, + Password: AppProps.Redis.Passwd, + DB: AppProps.Redis.Database, + }) + + // 开放 + RedisClient = redisdb + + // 心跳 + pong, err := redisdb.Ping().Result() + + // Output: PONG + Logger.Info(pong, err) +} + +func (p RedisPool) Test() { + ExampleClient_String() + ExampleClient_List() + ExampleClient_Hash() + ExampleClient_Set() + ExampleClient_SortSet() + ExampleClient_HyperLogLog() + ExampleClient_CMD() + ExampleClient_Scan() + ExampleClient_Tx() + ExampleClient_Script() + ExampleClient_PubSub() +} + +func ExampleClient_String() { + Logger.Println("ExampleClient_String") + defer Logger.Println("ExampleClient_String") + + //kv读写 + err := redisdb.Set("key", "value", 1*time.Second).Err() + Logger.Println(err) + + //获取过期时间 + tm, err := redisdb.TTL("key").Result() + Logger.Println(tm) + + val, err := redisdb.Get("key").Result() + Logger.Println(val, err) + + val2, err := redisdb.Get("missing_key").Result() + if err == redis.Nil { + Logger.Println("missing_key does not exist") + } else if err != nil { + Logger.Println("missing_key", val2, err) + } + + //不存在才设置 过期时间 nx ex + value, err := redisdb.SetNX("counter", 0, 1*time.Second).Result() + Logger.Println("setnx", value, err) + + //Incr + result, err := redisdb.Incr("counter").Result() + Logger.Println("Incr", result, err) +} + +func ExampleClient_List() { + Logger.Println("ExampleClient_List") + defer Logger.Println("ExampleClient_List") + + //添加 + Logger.Println(redisdb.RPush("list_test", "message1").Err()) + Logger.Println(redisdb.RPush("list_test", "message2").Err()) + + //设置 + Logger.Println(redisdb.LSet("list_test", 2, "message set").Err()) + + //remove + ret, err := redisdb.LRem("list_test", 3, "message1").Result() + Logger.Println(ret, err) + + rLen, err := redisdb.LLen("list_test").Result() + Logger.Println(rLen, err) + + //遍历 + lists, err := redisdb.LRange("list_test", 0, rLen-1).Result() + Logger.Println("LRange", lists, err) + + //pop没有时阻塞 + result, err := redisdb.BLPop(1*time.Second, "list_test").Result() + Logger.Println("result:", result, err, len(result)) +} + +func ExampleClient_Hash() { + Logger.Println("ExampleClient_Hash") + defer Logger.Println("ExampleClient_Hash") + + datas := map[string]interface{}{ + "name": "LI LEI", + "sex": 1, + "age": 28, + "tel": 123445578, + } + + //添加 + if err := redisdb.HMSet("hash_test", datas).Err(); err != nil { + Logger.Fatal(err) + } + + //获取 + rets, err := redisdb.HMGet("hash_test", "name", "sex").Result() + Logger.Println("rets:", rets, err) + + //成员 + retAll, err := redisdb.HGetAll("hash_test").Result() + Logger.Println("retAll", retAll, err) + + //存在 + bExist, err := redisdb.HExists("hash_test", "tel").Result() + Logger.Println(bExist, err) + + bRet, err := redisdb.HSetNX("hash_test", "id", 100).Result() + Logger.Println(bRet, err) + + //删除 + Logger.Println(redisdb.HDel("hash_test", "age").Result()) +} + +func ExampleClient_Set() { + Logger.Println("ExampleClient_Set") + defer Logger.Println("ExampleClient_Set") + + //添加 + ret, err := redisdb.SAdd("set_test", "11", "22", "33", "44").Result() + Logger.Println(ret, err) + + //数量 + count, err := redisdb.SCard("set_test").Result() + Logger.Println(count, err) + + //删除 + ret, err = redisdb.SRem("set_test", "11", "22").Result() + Logger.Println(ret, err) + + //成员 + members, err := redisdb.SMembers("set_test").Result() + Logger.Println(members, err) + + bret, err := redisdb.SIsMember("set_test", "33").Result() + Logger.Println(bret, err) + + redisdb.SAdd("set_a", "11", "22", "33", "44") + redisdb.SAdd("set_b", "11", "22", "33", "55", "66", "77") + //差集 + diff, err := redisdb.SDiff("set_a", "set_b").Result() + Logger.Println(diff, err) + + //交集 + inter, err := redisdb.SInter("set_a", "set_b").Result() + Logger.Println(inter, err) + + //并集 + union, err := redisdb.SUnion("set_a", "set_b").Result() + Logger.Println(union, err) + + ret, err = redisdb.SDiffStore("set_diff", "set_a", "set_b").Result() + Logger.Println(ret, err) + + rets, err := redisdb.SMembers("set_diff").Result() + Logger.Println(rets, err) +} + +func ExampleClient_SortSet() { + Logger.Println("ExampleClient_SortSet") + defer Logger.Println("ExampleClient_SortSet") + + addArgs := make([]redis.Z, 100) + for i := 1; i < 100; i++ { + addArgs = append(addArgs, redis.Z{Score: float64(i), Member: fmt.Sprintf("a_%d", i)}) + } + //Logger.Println(addArgs) + + Shuffle := func(slice []redis.Z) { + r := rand.New(rand.NewSource(time.Now().Unix())) + for len(slice) > 0 { + n := len(slice) + randIndex := r.Intn(n) + slice[n-1], slice[randIndex] = slice[randIndex], slice[n-1] + slice = slice[:n-1] + } + } + + //随机打乱 + Shuffle(addArgs) + + //添加 + ret, err := redisdb.ZAddNX("sortset_test", addArgs...).Result() + Logger.Println(ret, err) + + //获取指定成员score + score, err := redisdb.ZScore("sortset_test", "a_10").Result() + Logger.Println(score, err) + + //获取制定成员的索引 + index, err := redisdb.ZRank("sortset_test", "a_50").Result() + Logger.Println(index, err) + + count, err := redisdb.SCard("sortset_test").Result() + Logger.Println(count, err) + + //返回有序集合指定区间内的成员 + rets, err := redisdb.ZRange("sortset_test", 10, 20).Result() + Logger.Println(rets, err) + + //返回有序集合指定区间内的成员分数从高到低 + rets, err = redisdb.ZRevRange("sortset_test", 10, 20).Result() + Logger.Println(rets, err) + + //指定分数区间的成员列表 + rets, err = redisdb.ZRangeByScore("sortset_test", redis.ZRangeBy{Min: "(30", Max: "(50", Offset: 1, Count: 10}).Result() + Logger.Println(rets, err) +} + +//用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。 +//每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数 +func ExampleClient_HyperLogLog() { + Logger.Println("ExampleClient_HyperLogLog") + defer Logger.Println("ExampleClient_HyperLogLog") + + for i := 0; i < 10000; i++ { + redisdb.PFAdd("pf_test_1", fmt.Sprintf("pfkey%d", i)) + } + ret, err := redisdb.PFCount("pf_test_1").Result() + Logger.Println(ret, err) + + for i := 0; i < 10000; i++ { + redisdb.PFAdd("pf_test_2", fmt.Sprintf("pfkey%d", i)) + } + ret, err = redisdb.PFCount("pf_test_2").Result() + Logger.Println(ret, err) + + redisdb.PFMerge("pf_test", "pf_test_2", "pf_test_1") + ret, err = redisdb.PFCount("pf_test").Result() + Logger.Println(ret, err) +} + +func ExampleClient_PubSub() { + Logger.Println("ExampleClient_PubSub") + defer Logger.Println("ExampleClient_PubSub") + //发布订阅 + pubsub := redisdb.Subscribe("subkey") + _, err := pubsub.Receive() + if err != nil { + Logger.Fatal("pubsub.Receive") + } + ch := pubsub.Channel() + time.AfterFunc(1*time.Second, func() { + Logger.Println("Publish") + + err = redisdb.Publish("subkey", "test publish 1").Err() + if err != nil { + Logger.Fatal("redisdb.Publish", err) + } + + redisdb.Publish("subkey", "test publish 2") + }) + for msg := range ch { + Logger.Println("recv channel:", msg.Channel, msg.Pattern, msg.Payload) + } +} + +func ExampleClient_CMD() { + Logger.Println("ExampleClient_CMD") + defer Logger.Println("ExampleClient_CMD") + + //执行自定义redis命令 + Get := func(rdb *redis.Client, key string) *redis.StringCmd { + cmd := redis.NewStringCmd("get", key) + redisdb.Process(cmd) + return cmd + } + + v, err := Get(redisdb, "NewStringCmd").Result() + Logger.Println("NewStringCmd", v, err) + + v, err = redisdb.Do("get", "redisdb.do").String() + Logger.Println("redisdb.Do", v, err) +} + +func ExampleClient_Scan() { + Logger.Println("ExampleClient_Scan") + defer Logger.Println("ExampleClient_Scan") + + //scan + for i := 1; i < 1000; i++ { + redisdb.Set(fmt.Sprintf("skey_%d", i), i, 0) + } + + cusor := uint64(0) + for { + keys, retCusor, err := redisdb.Scan(cusor, "skey_*", int64(100)).Result() + Logger.Println(keys, cusor, err) + cusor = retCusor + if cusor == 0 { + break + } + } +} + +func ExampleClient_Tx() { + pipe := redisdb.TxPipeline() + incr := pipe.Incr("tx_pipeline_counter") + pipe.Expire("tx_pipeline_counter", time.Hour) + + // Execute + // + // MULTI + // INCR pipeline_counter + // EXPIRE pipeline_counts 3600 + // EXEC + // + // using one rdb-server roundtrip. + _, err := pipe.Exec() + fmt.Println(incr.Val(), err) +} + +func ExampleClient_Script() { + IncrByXX := redis.NewScript(` + if redis.call("GET", KEYS[1]) ~= false then + return redis.call("INCRBY", KEYS[1], ARGV[1]) + end + return false + `) + + n, err := IncrByXX.Run(redisdb, []string{"xx_counter"}, 2).Result() + fmt.Println(n, err) + + err = redisdb.Set("xx_counter", "40", 0).Err() + if err != nil { + panic(err) + } + + n, err = IncrByXX.Run(redisdb, []string{"xx_counter"}, 2).Result() + fmt.Println(n, err) +} diff --git a/src/main.go b/src/main.go new file mode 100644 index 0000000..fe36088 --- /dev/null +++ b/src/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "lingye-gin/src/config" + "lingye-gin/src/middleware" +) + +func main() { + // 初始化yaml配置 + new(config.ApplicationProperties).Init() + // 初始化redis + new(config.RedisPool).Init() + // 初始化gin + new(middleware.GinEngine).Start() +} diff --git a/src/middleware/gin_engine.go b/src/middleware/gin_engine.go new file mode 100644 index 0000000..1ac866b --- /dev/null +++ b/src/middleware/gin_engine.go @@ -0,0 +1,26 @@ +package middleware + +import ( + "fmt" + "github.com/gin-gonic/gin" + "lingye-gin/src/config" + "strings" +) + +type GinEngine struct { +} + +func (v *GinEngine) Start() { + if strings.Compare(config.AppProps.App.Mode, "debug") == 0 { + gin.SetMode(gin.DebugMode) + } else if strings.Compare(config.AppProps.App.Mode, "release") == 0 { + gin.SetMode(gin.ReleaseMode) + } + engine := gin.Default() + engine.Use(config.LoggerToFile()) + // 设置路由 + new(GinRouter).Init(engine) + config.Logger.Info("GinWebServer Init...") + _ = engine.Run(fmt.Sprintf("0.0.0.0:%d", config.AppProps.Server.Port)) + config.Logger.Info("GinWebServer Starting...") +} diff --git a/src/middleware/gin_router.go b/src/middleware/gin_router.go new file mode 100644 index 0000000..88b7cf4 --- /dev/null +++ b/src/middleware/gin_router.go @@ -0,0 +1,72 @@ +package middleware + +import ( + "fmt" + "github.com/gin-gonic/gin" + "lingye-gin/src/rest" + "strings" +) + +type GinRouter struct { +} + +func (v GinRouter) Init(r *gin.Engine) { + // 路由组映射关系 + groupMap := make(map[string]*gin.RouterGroup) + + for _, currentRa := range rest.Urls { + // 判断是否在某一组下 + if strings.Compare(currentRa.GroupName, "") == 0 { + // get + if strings.Compare(currentRa.Mode, "get") == 0 { + r.GET(currentRa.RelativePath, currentRa.HandleFunction) + continue + } + // post + if strings.Compare(currentRa.Mode, "post") == 0 { + r.POST(currentRa.RelativePath, currentRa.HandleFunction) + continue + } + // delete + if strings.Compare(currentRa.Mode, "delete") == 0 { + r.DELETE(currentRa.RelativePath, currentRa.HandleFunction) + continue + } + // put + if strings.Compare(currentRa.Mode, "put") == 0 { + r.PUT(currentRa.RelativePath, currentRa.HandleFunction) + } + } else { + // 分组名称 + groupName := fmt.Sprintf("/%s", currentRa.GroupName) + // 分组不存在 + if groupMap[currentRa.GroupName] == nil { + if currentRa.GroupHandleFunction == nil { + // 不存在分组过滤器 + groupMap[currentRa.GroupName] = r.Group(groupName) + } else { + groupMap[currentRa.GroupName] = r.Group(groupName, currentRa.GroupHandleFunction) + } + } + // get + if strings.Compare(currentRa.Mode, "get") == 0 { + groupMap[currentRa.GroupName].GET(currentRa.RelativePath, currentRa.HandleFunction) + continue + } + // post + if strings.Compare(currentRa.Mode, "post") == 0 { + groupMap[currentRa.GroupName].POST(currentRa.RelativePath, currentRa.HandleFunction) + continue + } + // delete + if strings.Compare(currentRa.Mode, "delete") == 0 { + groupMap[currentRa.GroupName].DELETE(currentRa.RelativePath, currentRa.HandleFunction) + continue + } + // put + if strings.Compare(currentRa.Mode, "put") == 0 { + groupMap[currentRa.GroupName].PUT(currentRa.RelativePath, currentRa.HandleFunction) + } + } + } +} diff --git a/src/rest/sign_rest.go b/src/rest/sign_rest.go new file mode 100644 index 0000000..dfd7576 --- /dev/null +++ b/src/rest/sign_rest.go @@ -0,0 +1,21 @@ +package rest + +import ( + "github.com/gin-gonic/gin" + "lingye-gin/src/util" + "net/url" + "strconv" +) + +func DescribeSign(c *gin.Context) { + ts := strconv.FormatInt(util.GetTimeUnix(), 10) + res := map[string]interface{}{} + params := url.Values{ + "username": []string{"lingye"}, + "age": []string{"10"}, + "ts": []string{ts}, + } + res["sn"] = util.CreateSign(params) + res["ts"] = ts + util.RetJson("200", "", res, c) +} diff --git a/src/rest/urls.go b/src/rest/urls.go new file mode 100644 index 0000000..f1ac1ce --- /dev/null +++ b/src/rest/urls.go @@ -0,0 +1,40 @@ +package rest + +import ( + "github.com/gin-gonic/gin" + v1 "lingye-gin/src/rest/v1" + v2 "lingye-gin/src/rest/v2" + "lingye-gin/src/util" +) + +type RequestApi struct { + // get、post、delete、put + Mode string + // 分组名称 + GroupName string + // 分组过滤器 + GroupHandleFunction gin.HandlerFunc + // 请求路径 + RelativePath string + // 请求处理器 + HandleFunction gin.HandlerFunc +} + +// 定义变长数组变量 +var Urls = [...]RequestApi{ + // 定义请求方式和路径 + {Mode: "get", RelativePath: "/sn", HandleFunction: DescribeSign}, + // v1 + // 获取所有学生 + {GroupName: "v1", Mode: "get", RelativePath: "/students", HandleFunction: v1.DescribeStudents}, + // 根据ID获取学生 + {GroupName: "v1", Mode: "get", RelativePath: "/students/:id", HandleFunction: v1.DescribeStudentById}, + // 保存学生 + {GroupName: "v1", Mode: "post", RelativePath: "/students", HandleFunction: v1.CreateStudent}, + // 根据ID更新学生 + {GroupName: "v1", Mode: "put", RelativePath: "/students/:id", HandleFunction: v1.ModifyStudentById}, + // 根据ID删除学生 + {GroupName: "v1", Mode: "delete", RelativePath: "/students/:id", HandleFunction: v1.DeleteStudentById}, + // v2 + {GroupName: "v2", GroupHandleFunction: util.VerifySign, Mode: "get", RelativePath: "/students", HandleFunction: v2.DescribeStudents}, +} diff --git a/src/rest/v1/student_rest.go b/src/rest/v1/student_rest.go new file mode 100644 index 0000000..bcfd64d --- /dev/null +++ b/src/rest/v1/student_rest.go @@ -0,0 +1,65 @@ +package v1 + +import ( + "fmt" + "github.com/gin-gonic/gin" +) + +type Student struct { + ID uint `json:"id"` + Username string `json:"username"` + Age uint `json:"age"` +} + +func DescribeStudents(c *gin.Context) { + var students []Student + c.JSON(200, gin.H{ + "data": students, + "page": 1, + "pageSize": 15, + }) +} + +func DescribeStudentById(c *gin.Context) { + id := c.Params.ByName("id") + fmt.Println("id=", id) + var student Student + if student.ID == 0 { + c.JSON(404, gin.H{"message": "student not found"}) + return + } + c.JSON(200, student) +} + +func CreateStudent(c *gin.Context) { + var student Student + // 绑定一个请求主体到一个类型 + _ = c.BindJSON(&student) + c.JSON(200, "创建成功") +} + +func ModifyStudentById(c *gin.Context) { + id := c.Params.ByName("id") + fmt.Println("id=", id) + var student Student + if student.ID == 0 { + c.JSON(404, gin.H{"message": "student not found"}) + return + } else { + _ = c.BindJSON(&student) + c.JSON(200, student) + } +} + +func DeleteStudentById(c *gin.Context) { + id := c.Params.ByName("id") + fmt.Println("id=", id) + var student Student + if student.ID == 0 { + c.JSON(404, gin.H{"message": "student not found"}) + return + } else { + _ = c.BindJSON(&student) + c.JSON(200, gin.H{"message": "delete success"}) + } +} diff --git a/src/rest/v2/student_rest.go b/src/rest/v2/student_rest.go new file mode 100644 index 0000000..170437d --- /dev/null +++ b/src/rest/v2/student_rest.go @@ -0,0 +1,20 @@ +package v2 + +import ( + "github.com/gin-gonic/gin" +) + +type Student struct { + ID uint `json:"id"` + Username string `json:"username"` + Age uint `json:"age"` +} + +func DescribeStudents(c *gin.Context) { + var students []Student + c.JSON(200, gin.H{ + "data": students, + "page": 1, + "pageSize": 15, + }) +} diff --git a/src/util/util.go b/src/util/util.go new file mode 100644 index 0000000..ce210f6 --- /dev/null +++ b/src/util/util.go @@ -0,0 +1,102 @@ +package util + +import ( + "crypto/md5" + "encoding/hex" + "fmt" + "github.com/gin-gonic/gin" + "lingye-gin/src/config" + "net/http" + "net/url" + "sort" + "strconv" + "time" +) + +// 打印 +func Print(i interface{}) { + fmt.Println("---") + fmt.Println(i) + fmt.Println("---") +} + +// 返回JSON +func RetJson(code, msg string, data interface{}, c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "code": code, + "msg": msg, + "data": data, + }) + c.Abort() +} + +// 获取当前时间戳 +func GetTimeUnix() int64 { + return time.Now().Unix() +} + +// MD5 方法 +func MD5(str string) string { + s := md5.New() + s.Write([]byte(str)) + return hex.EncodeToString(s.Sum(nil)) +} + +// 生成签名 +func CreateSign(params url.Values) string { + var key []string + var str = "" + for k := range params { + if k != "sn" { + key = append(key, k) + } + } + sort.Strings(key) + for i := 0; i < len(key); i++ { + if i == 0 { + str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i])) + } else { + str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i])) + } + } + // 自定义签名算法 + sign := MD5(MD5(str) + MD5(config.AppProps.App.Name+config.AppProps.App.Secret)) + return sign +} + +// 验证签名 +func VerifySign(c *gin.Context) { + var method = c.Request.Method + var ts int64 + var sn string + var req url.Values + + if method == "GET" { + req = c.Request.URL.Query() + sn = c.Query("sn") + ts, _ = strconv.ParseInt(c.Query("ts"), 10, 64) + + } else if method == "POST" { + _ = c.Request.ParseForm() + req = c.Request.PostForm + sn = c.PostForm("sn") + ts, _ = strconv.ParseInt(c.PostForm("ts"), 10, 64) + } else { + RetJson("500", "Illegal requests", "", c) + return + } + + exp, _ := strconv.ParseInt(config.AppProps.Server.Api.Expiry, 10, 64) + + // 验证过期时间 + if ts > GetTimeUnix() || GetTimeUnix()-ts >= exp { + RetJson("500", "Ts Error", "", c) + return + } + + // 验证签名 + if sn == "" || sn != CreateSign(req) { + RetJson("500", "Sn Error", "", c) + return + } +}