diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8d6f5cf --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +*.js linguist-language=GO +*.css linguist-language=GO +*.html linguist-language=GO \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..06618cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +.DS_Store +.buildpath +.hgignore.swp +.project +.orig +.swp +.idea/ +.settings/ +.vscode/ +vender/ +log/ +composer.lock +gitpush.sh +pkg/ +bin/ +cbuild +*/.DS_Store +config/config.toml +main +.vscode +go.sum +buildgf.bat +*.exe +*.exe~ +*.log diff --git a/02.hello/hello/go.mod b/02.hello/hello/go.mod new file mode 100644 index 0000000..ab1d9df --- /dev/null +++ b/02.hello/hello/go.mod @@ -0,0 +1,5 @@ +module gfhello + +go 1.14 + +require github.com/gogf/gf v1.11.7 diff --git a/02.hello/hello/hello.go b/02.hello/hello/hello.go new file mode 100644 index 0000000..d81d9fd --- /dev/null +++ b/02.hello/hello/hello.go @@ -0,0 +1,13 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf" + "github.com/gogf/gf/crypto/gmd5" +) + +func main() { + fmt.Println("hello world!") + fmt.Println(gf.VERSION) + fmt.Println(gmd5.EncryptString("123456")) +} diff --git a/02.hello/web/go.mod b/02.hello/web/go.mod new file mode 100644 index 0000000..d4eadc2 --- /dev/null +++ b/02.hello/web/go.mod @@ -0,0 +1,5 @@ +module gfweb + +go 1.14 + +require github.com/gogf/gf v1.11.7 diff --git a/02.hello/web/main.go b/02.hello/web/main.go new file mode 100644 index 0000000..9a375c2 --- /dev/null +++ b/02.hello/web/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.Writeln("Welcome GoFrame!!!") + }) + s.BindHandler("/hello", func(r *ghttp.Request) { + r.Response.Writeln("Hello World!") + }) + + s.SetPort(8080) + s.Run() +} diff --git a/03.web/config/config.toml b/03.web/config/config.toml new file mode 100644 index 0000000..357270b --- /dev/null +++ b/03.web/config/config.toml @@ -0,0 +1,19 @@ +[server] + # 端口号 + Address = ":8199" + # 静态目录 + ServerRoot = "public" + # 入口文件 + IndexFiles = ["index.html", "main.html"] + # 系统访问日志 + AccessLogEnabled = true + # 系统异常日志panic + ErrorLogEnabled = true + # 系统日志目录,启动,访问,异常 + LogPath = "gflogs" + +[logger] + # 标准日志目录 + path = "logs" + # 日志级别 + level = "all" \ No newline at end of file diff --git a/03.web/go.mod b/03.web/go.mod new file mode 100644 index 0000000..5f2562a --- /dev/null +++ b/03.web/go.mod @@ -0,0 +1,5 @@ +module gfweb2 + +go 1.14 + +require github.com/gogf/gf v1.11.7 diff --git a/03.web/main.go b/03.web/main.go new file mode 100644 index 0000000..769b051 --- /dev/null +++ b/03.web/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/os/glog" +) + +func main() { + s := g.Server() + // 测试日志 + s.BindHandler("/welcome", func(r *ghttp.Request) { + glog.Info("你来了!") + glog.Error("你异常啦!") + r.Response.Write("哈喽世界!") + }) + // 异常处理 + s.BindHandler("/panic", func(r *ghttp.Request) { + glog.Panic("123") + }) + // post请求 + s.BindHandler("POST:/hello", func(r *ghttp.Request) { + r.Response.Writeln("Hello World!") + }) + s.Run() +} diff --git a/03.web/public/hello.html b/03.web/public/hello.html new file mode 100644 index 0000000..b1fcade --- /dev/null +++ b/03.web/public/hello.html @@ -0,0 +1 @@ +welcome hello \ No newline at end of file diff --git a/03.web/public/index.html b/03.web/public/index.html new file mode 100644 index 0000000..6aced4a --- /dev/null +++ b/03.web/public/index.html @@ -0,0 +1 @@ +welcome static html \ No newline at end of file diff --git a/04.router/go.mod b/04.router/go.mod new file mode 100644 index 0000000..2804142 --- /dev/null +++ b/04.router/go.mod @@ -0,0 +1,5 @@ +module gf_router + +go 1.14 + +require github.com/gogf/gf v1.11.7 diff --git a/04.router/main.go b/04.router/main.go new file mode 100644 index 0000000..0f34a45 --- /dev/null +++ b/04.router/main.go @@ -0,0 +1,99 @@ +package main + +import ( + "github.com/gogf/gf/container/gtype" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +func main() { + s := g.Server() + // 常规注册 + // hello方法,post调用 + s.BindHandler("POST:/hello", func(r *ghttp.Request) { + r.Response.Writeln("url" + r.Router.Uri) + }) + // 所有方法,url包含name参数 + s.BindHandler("/:name", func(r *ghttp.Request) { + // 获取URL name参数 + r.Response.Writeln("name:" + r.GetString("name")) + r.Response.Writeln("url" + r.Router.Uri) + }) + // 所有方法,url包含name参数 + s.BindHandler("/:name/update", func(r *ghttp.Request) { + r.Response.Writeln("name:" + r.GetString("name")) + r.Response.Writeln("url" + r.Router.Uri) + }) + // 所有方法,url包含name和action参数 + s.BindHandler("/:name/:action", func(r *ghttp.Request) { + r.Response.Writeln("name:" + r.GetString("name")) + r.Response.Writeln("action:" + r.GetString("action")) + r.Response.Writeln("url" + r.Router.Uri) + }) + // 所有方法,url包含field属性 + s.BindHandler("/user/list/{field}.html", func(r *ghttp.Request) { + // 获取URL field属性 + r.Response.Writeln("field:" + r.GetString("field")) + r.Response.Writeln("url" + r.Router.Uri) + }) + + // 方法注册 + s.BindHandler("/total", Total) + + // 对象注册 + c := new(Controller) + s.BindObject("POST:/object", c) + + // 分组注册及中间件 + group := s.Group("/api") + group.Middleware(MiddlewareTest) + group.ALL("/all", func(r *ghttp.Request) { + r.Response.Writeln("all") + }) + group.GET("/get", func(r *ghttp.Request) { + r.Response.Writeln("get") + }) + group.POST("/post", func(r *ghttp.Request) { + r.Response.Writeln("post") + }) + + // request and response + s.BindHandler("POST:/test", func(r *ghttp.Request) { + r.Response.WriteJson(g.Map{ + "name": r.GetString("name"), + "age": r.GetInt("age"), + "sex": r.Header.Get("sex"), + }) + }) + + s.SetPort(8199) + s.Run() +} + +var ( + total = gtype.NewInt() +) + +func Total(r *ghttp.Request) { + r.Response.Write("total:", total.Add(1)) +} + +// 对象注册 +type Controller struct{} + +func (c *Controller) Index(r *ghttp.Request) { + r.Response.Write("index") +} + +func (c *Controller) Show(r *ghttp.Request) { + r.Response.Write("show") +} + +// 中间件 +func MiddlewareTest(r *ghttp.Request) { + // 前置逻辑 + r.Response.Writeln("###start") + r.Middleware.Next() + // 后置逻辑 + r.Response.Writeln("###end") +} diff --git a/04.router/test.http b/04.router/test.http new file mode 100644 index 0000000..9956608 --- /dev/null +++ b/04.router/test.http @@ -0,0 +1,43 @@ +### 常规注册 +POST http://localhost:8199/hello + +### +GET http://localhost:8199/abc + +### +GET http://localhost:8199/a/update + +### +GET http://localhost:8199/a/add + +### +GET http://localhost:8199/user/list/11.html + +### 方法注册 +GET http://localhost:8199/total + +### 对象注册,默认访问index +POST http://localhost:8199/object/ + +### 对象注册,直接访问Index +POST http://localhost:8199/object/index + +### 对象注册,访问show方法 +POST http://localhost:8199/object/show + +### 分组,默认访问index +PUT http://localhost:8199/api/all + +### 对象注册,直接访问Index +GET http://localhost:8199/api/get + +### 对象注册,访问show方法 +POST http://localhost:8199/api/post + +### request and response +POST http://localhost:8199/test +sex:man + +name=liubang&age=18 + +### diff --git a/05.client/go.mod b/05.client/go.mod new file mode 100644 index 0000000..da2b4a9 --- /dev/null +++ b/05.client/go.mod @@ -0,0 +1,5 @@ +module gfclient + +go 1.14 + +require github.com/gogf/gf v1.11.7 diff --git a/05.client/main.go b/05.client/main.go new file mode 100644 index 0000000..32787bf --- /dev/null +++ b/05.client/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +func main() { + s := g.Server() + group := s.Group("/api") + // 默认路径 + group.ALL("/", func(r *ghttp.Request) { + r.Response.Writeln("Welcome GoFrame!") + }) + // GET带参数 + group.GET("/hello", func(r *ghttp.Request) { + r.Response.Writeln("Hello World!") + r.Response.Writeln("name:", r.GetString("name")) + }) + // POST KV + group.POST("/test", func(r *ghttp.Request) { + r.Response.Writeln("func:test") + r.Response.Writeln("name:", r.GetString("name")) + r.Response.Writeln("age:", r.GetInt("age")) + }) + // POST JSON + group.POST("/test2", func(r *ghttp.Request) { + r.Response.Writeln("func:test2") + r.Response.Writeln("passport:", r.GetString("passport")) + r.Response.Writeln("password:", r.GetString("password")) + }) + // POST Header + group.POST("/test3", func(r *ghttp.Request) { + r.Response.Writeln("func:test3") + r.Response.Writeln("Cookie:", r.Header.Get("Cookie")) + }) + // POST Header + group.POST("/test4", func(r *ghttp.Request) { + r.Response.Writeln("func:test4") + h := r.Header + r.Response.Writeln("accept-encoding:", h.Get("accept-encoding")) + r.Response.Writeln("accept-language:", h.Get("accept-language")) + r.Response.Writeln("referer:", h.Get("referer")) + r.Response.Writeln("cookie:", h.Get("cookie")) + r.Response.Writeln(r.Cookie.Map()) + }) + + s.SetPort(80) + s.Run() +} diff --git a/05.client/test/client_test.go b/05.client/test/client_test.go new file mode 100644 index 0000000..ab4e0b2 --- /dev/null +++ b/05.client/test/client_test.go @@ -0,0 +1,84 @@ +package test + +import ( + "fmt" + "github.com/gogf/gf/net/ghttp" + "testing" +) + +var path = "http://127.0.0.1/api" + +// GET请求 +func TestGet(t *testing.T) { + if response, err := ghttp.Get(path); err != nil { + panic(err) + } else { + defer response.Close() + t.Log(response.ReadAllString()) + } + if response, err := ghttp.Post(path); err != nil { + panic(err) + } else { + defer response.Close() + t.Log(response.ReadAllString()) + } +} + +// GET请求带参数 +func TestHello(t *testing.T) { + if response, err := ghttp.Get(path + "/hello?name=whoami"); err != nil { + panic(err) + } else { + defer response.Close() + t.Log(response.ReadAllString()) + } +} + +// POST请求 +func TestPost(t *testing.T) { + if response, err := ghttp.Post(path+"/test", "name=john&age=18"); err != nil { + panic(err) + } else { + defer response.Close() + t.Log(response.ReadAllString()) + } +} + +// POST JSON +func TestPostJson(t *testing.T) { + if response, err := ghttp.Post(path+"/test2", + `{"passport":"john","password":"123456"}`); err != nil { + panic(err) + } else { + defer response.Close() + t.Log(response.ReadAllString()) + } +} + +// POST Header头 +func TestPostHeader(t *testing.T) { + c := ghttp.NewClient() + c.SetHeader("Cookie", "name=john; score=100") + if r, e := c.Post(path + "/test3"); e != nil { + panic(e) + } else { + fmt.Println(r.ReadAllString()) + } +} + +// POST Header头 +func TestPostHeader2(t *testing.T) { + c := ghttp.NewClient() + c.SetHeaderRaw(` +accept-encoding: gzip, deflate, br +accept-language: zh-CN,zh;q=0.9,en;q=0.8 +referer: https://idonottell.you +cookie: name=john; score=100 +user-agent: my test http client + `) + if r, e := c.Post(path + "/test4"); e != nil { + panic(e) + } else { + fmt.Println(r.ReadAllString()) + } +} diff --git a/06.config/config/config.toml b/06.config/config/config.toml new file mode 100644 index 0000000..7a6fe71 --- /dev/null +++ b/06.config/config/config.toml @@ -0,0 +1,29 @@ +# 模板引擎目录 +viewpath = "/home/www/templates/" +name = "hello world!" +# MySQL数据库配置 +[database] + [[database.default]] + host = "127.0.0.1" + port = "3306" + user = "root" + pass = "123456" + name = "test1" + type = "mysql" + role = "master" + charset = "utf8" + priority = "1" + [[database.default]] + host = "127.0.0.1" + port = "3306" + user = "root" + pass = "123456" + name = "test2" + type = "mysql" + role = "master" + charset = "utf8" + priority = "1" +# Redis数据库配置 +[redis] + disk = "127.0.0.1:6379,0" + cache = "127.0.0.1:6379,1" \ No newline at end of file diff --git a/06.config/configTest/config1.toml b/06.config/configTest/config1.toml new file mode 100644 index 0000000..a0c3b4c --- /dev/null +++ b/06.config/configTest/config1.toml @@ -0,0 +1,2 @@ +study = "hello study" +study1 = "hello study1" \ No newline at end of file diff --git a/06.config/configTest/config2.toml b/06.config/configTest/config2.toml new file mode 100644 index 0000000..ee65e68 --- /dev/null +++ b/06.config/configTest/config2.toml @@ -0,0 +1 @@ +config2 = "111" \ No newline at end of file diff --git a/06.config/config_test.go b/06.config/config_test.go new file mode 100644 index 0000000..5f29fc2 --- /dev/null +++ b/06.config/config_test.go @@ -0,0 +1,44 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf/frame/g" + "testing" +) + +// 基本配置使用 +func TestConfig(t *testing.T) { + // 默认当前路径或者config路径,默认文件config.toml + // /home/www/template/ + fmt.Println(g.Config().Get("viewpath")) + fmt.Println(g.Cfg().Get("viewpath")) + // 127.0.0.1:6379,1 + c := g.Cfg() + // 分组方式 + fmt.Println(c.Get("redis.cache")) + // 数组方式:test2 + fmt.Println(c.Get("database.default.1.name")) +} + +// 设置路径 +func TestConfig2(t *testing.T) { + // 设置加载文件,默认name为default + // 设置路径 + g.Cfg().SetPath("configTest") + // 设置加载文件 + g.Cfg().SetFileName("config1.toml") + + // 打印测试 + fmt.Println(g.Cfg().Get("viewpath")) + fmt.Println(g.Cfg().Get("study")) + fmt.Println(g.Cfg().Get("study1")) + fmt.Println(g.Cfg().Get("config2")) + + // 新的name就是新的实例 + g.Cfg("name").SetPath("configTest") + g.Cfg("name").SetFileName("config2.toml") + fmt.Println(g.Cfg("name").Get("viewpath")) + fmt.Println(g.Cfg("name").Get("study")) + fmt.Println(g.Cfg("name").Get("study1")) + fmt.Println(g.Cfg("name").Get("config2")) +} diff --git a/06.config/go.mod b/06.config/go.mod new file mode 100644 index 0000000..0d1bf0a --- /dev/null +++ b/06.config/go.mod @@ -0,0 +1,5 @@ +module gf_config + +go 1.14 + +require github.com/gogf/gf v1.11.7 diff --git a/06.config/main.go b/06.config/main.go new file mode 100644 index 0000000..372edb2 --- /dev/null +++ b/06.config/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +func main() { + s := g.Server() + // 默认路径 + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.Writeln("配置", g.Config().GetString("name")) + r.Response.Writeln("Welcome GoFrame!") + }) + + s.SetPort(80) + s.Run() + +} diff --git a/07.log/config/config.toml b/07.log/config/config.toml new file mode 100644 index 0000000..56b9ff9 --- /dev/null +++ b/07.log/config/config.toml @@ -0,0 +1,17 @@ +[logger] + # 日志目录 + path = "logs" + # all LEVEL_ALL = LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT + # dev LEVEL_DEV = LEVEL_ALL + # pro LEVEL_PROD = LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT + level = "all" + # 是否打印到控制台 + stdout = true + [logger.logger1] + path = "logger1" + level = "dev" + stdout = true + [logger.logger2] + path = "logger2" + level = "prod" + stdout = false \ No newline at end of file diff --git a/07.log/go.mod b/07.log/go.mod new file mode 100644 index 0000000..19e2dbc --- /dev/null +++ b/07.log/go.mod @@ -0,0 +1,5 @@ +module gf_log + +go 1.14 + +require github.com/gogf/gf v1.11.7 diff --git a/07.log/main.go b/07.log/main.go new file mode 100644 index 0000000..e4c96d0 --- /dev/null +++ b/07.log/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/os/glog" +) + +func main() { + // 对应默认配置项 logger,默认default + g.Log().Debug("[default]Debug") + g.Log().Info("[default]info") + g.Log().Warning("[default]Warning") + g.Log().Error("[default]Error") + // 对应 logger.logger1 配置项 + g.Log("logger1").Debug("[logger1]Debug") + g.Log("logger1").Info("[logger1]info") + g.Log("logger1").Warning("[logger1]Warning") + g.Log("logger1").Error("[logger1]Error") + // 对应 logger.logger2 配置项 + g.Log("logger2").Debug("[logger2]Debug") + g.Log("logger2").Info("[logger2]info") + g.Log("logger2").Warning("[logger2]Warning") + g.Log("logger2").Error("[logger2]Error") + + // 日志级别设置,过滤掉Info日志信息 + l := glog.New() + l.Info("info1") + l.SetLevel(glog.LEVEL_ALL ^ glog.LEVEL_INFO) + l.Info("info2") + // 支持哪些级别 + // LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT + + // 异常 + g.Log().Panic("this is panic!") + g.Log().Info("............") + +} diff --git a/08.database/config/config.toml b/08.database/config/config.toml new file mode 100644 index 0000000..47fbc6c --- /dev/null +++ b/08.database/config/config.toml @@ -0,0 +1,3 @@ +# 数据库配置 +[database] + link = "mysql:root:A123456@tcp(127.0.0.1:13306)/test" \ No newline at end of file diff --git a/08.database/db_test.go b/08.database/db_test.go new file mode 100644 index 0000000..66394bd --- /dev/null +++ b/08.database/db_test.go @@ -0,0 +1,72 @@ +package test + +import ( + "fmt" + "github.com/gogf/gf/frame/g" + "testing" +) + +// Insert +func TestInsert(t *testing.T) { + // INSERT INTO `user`(`name`) VALUES('john') + _, err := g.DB().Table("user").Data(g.Map{"uid": 10000, "name": "john"}).Insert() + if err != nil { + panic(err) + } +} + +// Update +func TestUpdate(t *testing.T) { + // UPDATE `user` SET `name`='john guo' WHERE name='john' + _, err := g.DB().Table("user").Data("name", "john guo"). + Where("name", "john").Update() + if err != nil { + panic(err) + } +} + +// Delete +func TestDelete(t *testing.T) { + // DELETE FROM `user` WHERE uid=10 + _, err := g.DB().Table("user").Where("uid", 10000).Delete() + if err != nil { + panic(err) + } +} + +// Select Where +func TestWhere(t *testing.T) { + // INSERT INTO `user`(`name`) VALUES('john') + g.DB().Table("user").Data(g.Map{"uid": 10001, "name": "john"}).Insert() + g.DB().Table("user").Data(g.Map{"uid": 10002, "name": "john2"}).Insert() + // 数量 + count, err := g.DB().Table("user").Where("uid", 10001).Count() + if err != nil { + panic(err) + } + fmt.Println("count:", count) + // 获取单个值 + v, err := g.DB().Table("user").Where("uid", 10001).Fields("name").Value() + if err != nil { + panic(err) + } + fmt.Println("name:", v.String()) + // 查询对象 + r, err := g.DB().Table("user").Where("uid", 10002).One() + if err != nil { + panic(err) + } + fmt.Println("name:", r.Map()["name"]) + // 查询对象 + //l, err := g.DB().Table("user").As("t").Where("t.uid > ?", 10000).All() + // 也可以简写为 select * from user as t where t.uid > 10000 + l, err := g.DB().Table("user").As("t").All("t.uid > ?", 10000) + if err != nil { + panic(err) + } + for index, value := range l { + fmt.Println(index, value["uid"], value["name"]) + } + g.DB().Table("user").Where("uid", 10001).Delete() + g.DB().Table("user").Where("uid", 10002).Delete() +} diff --git a/08.database/go.mod b/08.database/go.mod new file mode 100644 index 0000000..eacdbc1 --- /dev/null +++ b/08.database/go.mod @@ -0,0 +1,5 @@ +module gf_database + +go 1.14 + +require github.com/gogf/gf v1.11.7 diff --git a/09.redis/config/config.toml b/09.redis/config/config.toml new file mode 100644 index 0000000..6c311e6 --- /dev/null +++ b/09.redis/config/config.toml @@ -0,0 +1,4 @@ +# Redis数据库配置 +[redis] + default = "127.0.0.1:6379,0" + cache = "127.0.0.1:6379,1,123456?idleTimeout=600" \ No newline at end of file diff --git a/09.redis/go.mod b/09.redis/go.mod new file mode 100644 index 0000000..d9ddf56 --- /dev/null +++ b/09.redis/go.mod @@ -0,0 +1,5 @@ +module gf_redis + +go 1.14 + +require github.com/gogf/gf v1.11.7 diff --git a/09.redis/main.go b/09.redis/main.go new file mode 100644 index 0000000..64e4b7d --- /dev/null +++ b/09.redis/main.go @@ -0,0 +1,47 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/util/gconv" +) + +func main() { + // redis字符串操作 + g.Redis().Do("SET", "k", "v") + v, _ := g.Redis().Do("GET", "k") + g.Log().Info(gconv.String(v)) + + // 获取cache链接 + v2, _ := g.Redis("cache").Do("GET", "k") + g.Log().Info(gconv.String(v2)) + + // DoVar转换 + v3, _ := g.Redis().DoVar("GET", "k") + g.Log().Info(v3.String()) + + // setex + g.Redis().Do("SETEX", "keyEx", 2000, "v4") + v4, _ := g.Redis().DoVar("GET", "keyEx") + g.Log().Info(v4.String()) + + // list + g.Redis().Do("RPUSH", "keyList", "v5") + v5, _ := g.Redis().DoVar("LPOP", "keyList") + g.Log().Info(v5.String()) + + // hash + g.Redis().Do("HSET", "keyHash", "v1", "v6") + v6, _ := g.Redis().DoVar("HGET", "keyHash", "v1") + g.Log().Info(v6.String()) + + // set + g.Redis().Do("SADD", "keySet", "v7") + v7, _ := g.Redis().DoVar("SPOP", "keySet") + g.Log().Info(v7.String()) + + // sort set + g.Redis().Do("ZADD", "keySortSet", 1, "v8") + v8, _ := g.Redis().DoVar("ZREM", "keySortSet", "v8") + g.Log().Info(v8.Int()) + +} diff --git a/10.tools/go.mod b/10.tools/go.mod new file mode 100644 index 0000000..7bd8af5 --- /dev/null +++ b/10.tools/go.mod @@ -0,0 +1,5 @@ +module gf_tools + +go 1.14 + +require github.com/gogf/gf v1.11.7 diff --git a/10.tools/tools_test.go b/10.tools/tools_test.go new file mode 100644 index 0000000..9837829 --- /dev/null +++ b/10.tools/tools_test.go @@ -0,0 +1,201 @@ +package test + +import ( + "fmt" + "github.com/gogf/gf/container/gmap" + "github.com/gogf/gf/crypto/gmd5" + "github.com/gogf/gf/encoding/gjson" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/text/gstr" + "github.com/gogf/gf/util/gconv" + "testing" +) + +// gstr 示例 +func TestStr(t *testing.T) { + p := fmt.Println + p("Contains: ", gstr.Contains("test", "es")) + p("Count: ", gstr.Count("test", "t")) + p("HasPrefix: ", gstr.HasPrefix("test", "te")) + p("HasSuffix: ", gstr.HasSuffix("test", "st")) + p("Join: ", gstr.Join([]string{"a", "b"}, "-")) + p("Repeat: ", gstr.Repeat("a", 5)) + p("Replace: ", gstr.Replace("foo", "o", "0", -1)) + p("Replace: ", gstr.Replace("foo", "o", "0", 1)) + p("Split: ", gstr.Split("a-b-c-d-e", "-")) + p("ToLower: ", gstr.ToLower("TEST")) + p("ToUpper: ", gstr.ToUpper("test")) + p("Trim: ", gstr.Trim(" test ")) +} + +func TestMap(t *testing.T) { + p := fmt.Println + // 常规map方法 + // 初始化 + m2 := g.Map{"a": 1, "b": 2} + p(m2) + // 设置 + m2["c"] = 25 + p(m2) + // 获取 + p(m2["b"]) + // 删除 + delete(m2, "c") + // 遍历 + for k, v := range m2 { + p(k, v) + } + + p("###########################") + + // 创建一个默认的gmap对象, + // 默认情况下该gmap对象不支持并发安全特性, + // 初始化时可以给定true参数开启并发安全特性。 + m := gmap.New() + // 设置键值对 + for i := 0; i < 10; i++ { + m.Set(i, i) + } + // 查询大小 + p(m.Size()) + // 批量设置键值对(不同的数据类型对象参数不同) + m.Sets(map[interface{}]interface{}{ + 10: 10, + 11: 11, + }) + p(m.Size()) + // 查询是否存在 + p(m.Contains(1)) + // 查询键值 + p(m.Get(1)) + // 删除数据项 + m.Remove(9) + p(m.Size()) + // 批量删除 + m.Removes([]interface{}{10, 11}) + p(m.Size()) + // 当前键名列表(随机排序) + p(m.Keys()) + // 当前键值列表(随机排序) + p(m.Values()) + // 查询键名,当键值不存在时,写入给定的默认值 + p(m.GetOrSet(100, 100)) + // 删除键值对,并返回对应的键值 + p(m.Remove(100)) + // 遍历map + m.Iterator(func(k interface{}, v interface{}) bool { + fmt.Printf("%v:%v ", k, v) + return true + }) + // 清空map + m.Clear() + // 判断map是否为空 + p(m.IsEmpty()) +} + +func TestJson(t *testing.T) { + p := fmt.Println + // 创建json + jsonContent := `{"name":"john", "score":"100"}` + j := gjson.New(jsonContent) + p(j.Get("name")) + p(j.Get("score")) + + // 创建json + j2 := gjson.New(nil) + j2.Set("name", "John") + j2.Set("score", 99.5) + fmt.Printf( + "Name: %s, Score: %v\n", + j2.GetString("name"), + j2.GetFloat32("score"), + ) + p(j2.MustToJsonString()) + + // struct转json + type Me struct { + Name string `json:"name"` + Score int `json:"score"` + } + me := Me{ + Name: "john", + Score: 100, + } + j3 := gjson.New(me) + p(j3.Get("name")) + p(j3.Get("score")) + // 转换回Struct + Me2 := new(Me) + if err := j.ToStruct(Me2); err != nil { + panic(err) + } + fmt.Printf(`%+v`, Me2) + p() + + // 格式转换 + p("JSON:") + p(j3.MustToJsonString()) + p("======================") + + p("XML:") + p(j3.MustToXmlString("document")) + p("======================") + + p("YAML:") + p(j3.MustToYamlString()) + p("======================") + + p("TOML:") + p(j3.MustToTomlString()) +} + +func TestMd5(t *testing.T) { + p := fmt.Println + // md5加密 + p(gmd5.MustEncrypt("123456")) +} + +func TestConv(t *testing.T) { + i := 123.456 + fmt.Printf("%10s %v\n", "Int:", gconv.Int(i)) + fmt.Printf("%10s %v\n", "Int8:", gconv.Int8(i)) + fmt.Printf("%10s %v\n", "Int16:", gconv.Int16(i)) + fmt.Printf("%10s %v\n", "Int32:", gconv.Int32(i)) + fmt.Printf("%10s %v\n", "Int64:", gconv.Int64(i)) + fmt.Printf("%10s %v\n", "Uint:", gconv.Uint(i)) + fmt.Printf("%10s %v\n", "Uint8:", gconv.Uint8(i)) + fmt.Printf("%10s %v\n", "Uint16:", gconv.Uint16(i)) + fmt.Printf("%10s %v\n", "Uint32:", gconv.Uint32(i)) + fmt.Printf("%10s %v\n", "Uint64:", gconv.Uint64(i)) + fmt.Printf("%10s %v\n", "Float32:", gconv.Float32(i)) + fmt.Printf("%10s %v\n", "Float64:", gconv.Float64(i)) + fmt.Printf("%10s %v\n", "Bool:", gconv.Bool(i)) + fmt.Printf("%10s %v\n", "String:", gconv.String(i)) + + fmt.Printf("%10s %v\n", "Bytes:", gconv.Bytes(i)) + fmt.Printf("%10s %v\n", "Strings:", gconv.Strings(i)) + fmt.Printf("%10s %v\n", "Ints:", gconv.Ints(i)) + fmt.Printf("%10s %v\n", "Floats:", gconv.Floats(i)) + fmt.Printf("%10s %v\n", "Interfaces:", gconv.Interfaces(i)) + + fmt.Println("##############") + // struct和map转换 + type User struct { + Uid int `c:"uid"` + Name string `c:"name"` + } + // 对象 + m := gconv.Map(User{ + Uid: 1, + Name: "john", + }) + fmt.Println(m) + + fmt.Println("##############") + user := (*User)(nil) + err := gconv.Struct(m, &user) + if err != nil { + panic(err) + } + g.Dump(user) +} diff --git a/11.template/config/config.toml b/11.template/config/config.toml new file mode 100644 index 0000000..a26e494 --- /dev/null +++ b/11.template/config/config.toml @@ -0,0 +1,6 @@ + +# 模板引擎配置 +[viewer] + Path = "template" + DefaultFile = "index.html" + Delimiters = ["${", "}"] diff --git a/11.template/go.mod b/11.template/go.mod new file mode 100644 index 0000000..c7c1947 --- /dev/null +++ b/11.template/go.mod @@ -0,0 +1,5 @@ +module gf-template + +go 1.14 + +require github.com/gogf/gf v1.12.1 diff --git a/11.template/main.go b/11.template/main.go new file mode 100644 index 0000000..f61c450 --- /dev/null +++ b/11.template/main.go @@ -0,0 +1,47 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +func main() { + s := g.Server() + // 常规注册 + group := s.Group("/") + + // 模板文件 + group.GET("/", func(r *ghttp.Request) { + r.Response.WriteTpl("index.html", g.Map{ + "title": "列表页面", + "show": true, + "listData": g.List{ + g.Map{ + "date": "2020-04-01", + "name": "朱元璋", + "address": "江苏110号", + }, + g.Map{ + "date": "2020-04-02", + "name": "徐达", + "address": "江苏111号", + }, + g.Map{ + "date": "2020-04-03", + "name": "李善长", + "address": "江苏112号", + }, + }}) + }) + + // 字符串传入 + group.GET("/template", func(r *ghttp.Request) { + tplContent := `id:${.id}, name:${.name}` + r.Response.WriteTplContent(tplContent, g.Map{ + "id" : 123, + "name" : "john", + }) + }) + + s.Run() +} diff --git a/11.template/template/include.html b/11.template/template/include.html new file mode 100644 index 0000000..f31bf1e --- /dev/null +++ b/11.template/template/include.html @@ -0,0 +1,5 @@ + + + 这里是通过include引用的文件内容 + + diff --git a/11.template/template/index.html b/11.template/template/index.html new file mode 100644 index 0000000..b10424b --- /dev/null +++ b/11.template/template/index.html @@ -0,0 +1,89 @@ + + + + + + + + + +
+ + + ${ .title } + ${if .show}【展示】${end} + + + + + + + + + + + ${"我是中国人" | substr 2 -1} + + + + ${include "include.html" .} + +
+ + + + + + + + + + diff --git a/12.login/config/config.toml b/12.login/config/config.toml new file mode 100644 index 0000000..142e0dd --- /dev/null +++ b/12.login/config/config.toml @@ -0,0 +1,10 @@ +# 账号 +username = "admin" +# 密码 +password = "123456" + +# 模板引擎配置 +[viewer] + Path = "template" + DefaultFile = "index.html" + Delimiters = ["${", "}"] \ No newline at end of file diff --git a/12.login/go.mod b/12.login/go.mod new file mode 100644 index 0000000..82b7344 --- /dev/null +++ b/12.login/go.mod @@ -0,0 +1,5 @@ +module gf-login11 + +go 1.14 + +require github.com/gogf/gf v1.12.1 diff --git a/12.login/main.go b/12.login/main.go new file mode 100644 index 0000000..bf10688 --- /dev/null +++ b/12.login/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +func main() { + s := g.Server() + // 常规注册 + group := s.Group("/") + // 登录页面 + group.GET("/", func(r *ghttp.Request) { + r.Response.WriteTpl("index.html", g.Map{ + "title": "登录页面", + }) + }) + // 登录接口 + group.POST("/login", func(r *ghttp.Request) { + username := r.GetString("username") + password := r.GetString("password") + + //dbUsername := "admin" + //dbPassword := "123456" + dbUsername := g.Config().GetString("username") + dbPassword := g.Config().GetString("password") + if username == dbUsername && password == dbPassword { + r.Response.WriteJson(g.Map{ + "code": 0, + "msg": "登录成功", + }) + r.Exit() + } + + r.Response.WriteJson(g.Map{ + "code": -1, + "msg": "登录失败", + }) + }) + // 列表页面 + group.GET("/user/index", func(r *ghttp.Request) { + r.Response.WriteTpl("user_index.html", g.Map{ + "title": "列表页面", + "dataList": g.List{ + g.Map{ + "date": "2020-04-01", + "name": "朱元璋", + "address": "江苏110号", + }, + g.Map{ + "date": "2020-04-02", + "name": "徐达", + "address": "江苏111号", + }, + g.Map{ + "date": "2020-04-03", + "name": "李善长", + "address": "江苏112号", + }, + }}) + }) + // 登出接口 + group.POST("/logout", func(r *ghttp.Request) { + r.Response.WriteJson(g.Map{ + "code": 0, + "msg": "登出成功", + }) + }) + + s.Run() +} diff --git a/12.login/template/index.html b/12.login/template/index.html new file mode 100644 index 0000000..e9179e7 --- /dev/null +++ b/12.login/template/index.html @@ -0,0 +1,76 @@ + + + + + + + + + +
+ + + ${ .title } + + + + + + + + + + + + + + + 登录 + + + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/12.login/template/user_index.html b/12.login/template/user_index.html new file mode 100644 index 0000000..94999c1 --- /dev/null +++ b/12.login/template/user_index.html @@ -0,0 +1,105 @@ + + + + + + + + + +
+ + + ${ .title } + + + + + + + + + + 登出 + + + + +
+ + + + + + + + + + diff --git a/14.gsession/config/config.toml b/14.gsession/config/config.toml new file mode 100644 index 0000000..14ef89a --- /dev/null +++ b/14.gsession/config/config.toml @@ -0,0 +1,24 @@ +# 账号 +username = "admin" +# 密码 +password = "123456" + +# session存储方式file,memory,redis +# SessionStorage = "redis" + +[server] + Address = ":80" + SessionIdName = "gSessionId" + SessionPath = "./gession" + SessionMaxAge = "1m" + DumpRouterMap = true + +# 模板引擎配置 +[viewer] + Path = "template" + DefaultFile = "index.html" + Delimiters = ["${", "}"] + +# Redis数据库配置 +[redis] + default = "192.168.31.128:6379,0" \ No newline at end of file diff --git a/14.gsession/go.mod b/14.gsession/go.mod new file mode 100644 index 0000000..f0c9c93 --- /dev/null +++ b/14.gsession/go.mod @@ -0,0 +1,5 @@ +module gf-gsession + +go 1.14 + +require github.com/gogf/gf v1.12.1 diff --git a/14.gsession/main.go b/14.gsession/main.go new file mode 100644 index 0000000..5ff3874 --- /dev/null +++ b/14.gsession/main.go @@ -0,0 +1,110 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/os/gsession" +) + +const SessionUser = "SessionUser" + +func main() { + s := g.Server() + + // 设置存储方式 + sessionStorage := g.Config().GetString("SessionStorage") + if sessionStorage == "redis" { + s.SetConfigWithMap(g.Map{ + "SessionIdName": g.Config().GetString("server.SessionIdName"), + "SessionStorage": gsession.NewStorageRedis(g.Redis()), + }) + } else if sessionStorage == "memory" { + s.SetConfigWithMap(g.Map{ + "SessionStorage": gsession.NewStorageMemory(), + }) + } + + // 常规注册 + group := s.Group("/") + group.GET("/", func(r *ghttp.Request) { + r.Response.WriteTpl("index.html", g.Map{ + "title": "登录页面", + }) + }) + group.POST("/login", func(r *ghttp.Request) { + username := r.GetString("username") + password := r.GetString("password") + + //dbUsername := "admin" + //dbPassword := "123456" + dbUsername := g.Config().GetString("username") + dbPassword := g.Config().GetString("password") + if username == dbUsername && password == dbPassword { + // 添加session + r.Session.Set(SessionUser, g.Map{ + "username": dbUsername, + "name": "管理员", + }) + r.Response.WriteJson(g.Map{ + "code": 0, + "msg": "登录成功", + }) + r.Exit() + } + + r.Response.WriteJson(g.Map{ + "code": -1, + "msg": "登录失败", + }) + }) + + // 用户组 + userGroup := s.Group("/user") + userGroup.Middleware(MiddlewareAuth) + // 列表页面 + userGroup.GET("/index", func(r *ghttp.Request) { + r.Response.WriteTpl("user_index.html", g.Map{ + "title": "列表页面", + "dataList": g.List{ + g.Map{ + "date": "2020-04-01", + "name": "朱元璋", + "address": "江苏110号", + }, + g.Map{ + "date": "2020-04-02", + "name": "徐达", + "address": "江苏111号", + }, + g.Map{ + "date": "2020-04-03", + "name": "李善长", + "address": "江苏112号", + }, + }}) + }) + userGroup.POST("/logout", func(r *ghttp.Request) { + // 删除session + r.Session.Remove(SessionUser) + + r.Response.WriteJson(g.Map{ + "code": 0, + "msg": "登出成功", + }) + }) + + s.Run() +} + +// 认证中间件 +func MiddlewareAuth(r *ghttp.Request) { + if r.Session.Contains(SessionUser) { + r.Middleware.Next() + } else { + // 获取用错误码 + r.Response.WriteJson(g.Map{ + "code": 403, + "msg": "您访问超时或已登出", + }) + } +} diff --git a/14.gsession/template/index.html b/14.gsession/template/index.html new file mode 100644 index 0000000..e9179e7 --- /dev/null +++ b/14.gsession/template/index.html @@ -0,0 +1,76 @@ + + + + + + + + + +
+ + + ${ .title } + + + + + + + + + + + + + + + 登录 + + + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/14.gsession/template/user_index.html b/14.gsession/template/user_index.html new file mode 100644 index 0000000..ebb1b18 --- /dev/null +++ b/14.gsession/template/user_index.html @@ -0,0 +1,105 @@ + + + + + + + + + +
+ + + ${ .title } + + + + + + + + + + 登出 + + + + +
+ + + + + + + + + + diff --git a/14.gsession/test/test.http b/14.gsession/test/test.http new file mode 100644 index 0000000..60a46ec --- /dev/null +++ b/14.gsession/test/test.http @@ -0,0 +1,5 @@ +GET http://127.0.0.1/user/index +Cookie: gSessionId=C24J0ONC99ECJPEWBE + + +### \ No newline at end of file diff --git a/15.gvalid/go.mod b/15.gvalid/go.mod new file mode 100644 index 0000000..8e96d7a --- /dev/null +++ b/15.gvalid/go.mod @@ -0,0 +1,5 @@ +module gf-gvalid + +go 1.14 + +require github.com/gogf/gf v1.12.1 diff --git a/15.gvalid/valid_test.go b/15.gvalid/valid_test.go new file mode 100644 index 0000000..03ca96e --- /dev/null +++ b/15.gvalid/valid_test.go @@ -0,0 +1,73 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/util/gvalid" + "testing" +) + +// 单条校验 +func TestCheck(t *testing.T) { + rule := "length:6,16" + if m := gvalid.Check("12345", rule, nil); m != nil { + t.Log(m) + } else { + t.Log("check ok!") + } +} + +// map校验 +func TestCheckMap(t *testing.T) { + params := map[string]interface{}{ + "passport": "john", + "password": "123456", + "password2": "1234567", + } + rules := map[string]string{ + "passport": "required|length:6,16", + "password": "required|length:6,16|same:password2", + "password2": "required|length:6,16", + } + msgs := map[string]interface{}{ + "passport": "账号不能为空|账号长度应当在:min到:max之间", + "password": map[string]string{ + "required": "密码不能为空", + "same": "两次密码输入不相等", + }, + } + if e := gvalid.CheckMap(params, rules, msgs); e != nil { + fmt.Println("#############") + g.Dump(e.FirstItem()) + fmt.Println("#############") + g.Dump(e.FirstRule()) + fmt.Println("#############") + g.Dump(e.FirstString()) + fmt.Println("#############") + g.Dump(e.Map()) + fmt.Println("#############") + g.Dump(e.Maps()) + fmt.Println("#############") + g.Dump(e.String()) + fmt.Println("#############") + g.Dump(e.Strings()) + } else { + t.Log("check ok!") + } +} + +// 对象校验 +func TestCheckStruct(t *testing.T) { + type User struct { + Uid int `gvalid:"uid @integer|min:1#用户UID不能为空"` + Name string `gvalid:"name @required|length:6,30#请输入用户名称|用户名称长度非法"` + } + + user := &User{ + Name: "john", + } + + // 使用结构体定义的校验规则和错误提示进行校验 + g.Dump(gvalid.CheckStruct(user, nil).Map()) + +} diff --git a/16.secure/bcrypt_test.go b/16.secure/bcrypt_test.go new file mode 100644 index 0000000..b6966ca --- /dev/null +++ b/16.secure/bcrypt_test.go @@ -0,0 +1,59 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf/crypto/gmd5" + "golang.org/x/crypto/bcrypt" + "testing" +) + +func TestMd5(t *testing.T) { + md5, _ := gmd5.EncryptString("123456") + fmt.Println(md5) +} + +func TestMd5Salt(t *testing.T) { + md5, _ := gmd5.EncryptString("123456") + fmt.Println(md5) + fmt.Println(gmd5.EncryptString(md5 + "123456")) +} + +func TestBcrypt(t *testing.T) { + passwordOK := "123456" + passwordOK, _ = gmd5.EncryptString(passwordOK) + passwordERR := "12345678" + passwordERR, _ = gmd5.EncryptString(passwordERR) + + hash, err := bcrypt.GenerateFromPassword([]byte(passwordOK), bcrypt.DefaultCost) + if err != nil { + fmt.Println(err) + } + //fmt.Println(hash) + + encodePW := string(hash) // 保存在数据库的密码,虽然每次生成都不同,只需保存一份即可 + fmt.Println("###", encodePW) + hash, err = bcrypt.GenerateFromPassword([]byte(passwordOK), bcrypt.DefaultCost) + if err != nil { + fmt.Println(err) + } + encodePW = string(hash) // 保存在数据库的密码,虽然每次生成都不同,只需保存一份即可 + fmt.Println("###", encodePW) + // 其中:$是分割符,无意义;2a是bcrypt加密版本号;10是cost的值;而后的前22位是salt值; + // 再然后的字符串就是密码的密文了。 + + // 正确密码验证 + err = bcrypt.CompareHashAndPassword([]byte(encodePW), []byte(passwordOK)) + if err != nil { + fmt.Println("pw wrong") + } else { + fmt.Println("pw ok") + } + + // 错误密码验证 + err = bcrypt.CompareHashAndPassword([]byte(encodePW), []byte(passwordERR)) + if err != nil { + fmt.Println("pw wrong") + } else { + fmt.Println("pw ok") + } +} diff --git a/16.secure/config/config.toml b/16.secure/config/config.toml new file mode 100644 index 0000000..36b38ef --- /dev/null +++ b/16.secure/config/config.toml @@ -0,0 +1,41 @@ +# session存储方式file,memory,redis +SessionStorage = "redis" + +[server] + Address = ":80" + ServerRoot = "public" + SessionIdName = "gSessionId" + SessionPath = "./gession" + SessionMaxAge = "1m" + DumpRouterMap = true + # 系统访问日志 + AccessLogEnabled = true + # 系统异常日志panic + ErrorLogEnabled = true + # 系统日志目录,启动,访问,异常 + LogPath = "gflogs" + +[logger] + # 标准日志目录 + path = "logs" + # 日志级别 + level = "all" + +# 模板引擎配置 +[viewer] + Path = "template" + DefaultFile = "index.html" + Delimiters = ["${", "}"] + +# Redis数据库配置 +[redis] + default = "192.168.31.128:6379,0" + +[database] + [database.logger] + Path = "./dblogs" + Level = "all" + Stdout = true + [database.default] + link = "mysql:root:123456@tcp(192.168.31.128:3306)/gf-login" + debug = true \ No newline at end of file diff --git a/16.secure/config/server.crt b/16.secure/config/server.crt new file mode 100644 index 0000000..6c333d7 --- /dev/null +++ b/16.secure/config/server.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDozCCAougAwIBAgIUP/sMIvrPakKRFSBFxyWzpJ1HIHUwDQYJKoZIhvcNAQEL +BQAwYTELMAkGA1UEBhMCYmoxCzAJBgNVBAgMAmJqMQswCQYDVQQHDAJiajELMAkG +A1UECgwCYmoxCzAJBgNVBAsMAmJqMQswCQYDVQQDDAJiajERMA8GCSqGSIb3DQEJ +ARYCYmowHhcNMjAwNDE0MTYxNzQ0WhcNMjEwNDE0MTYxNzQ0WjBhMQswCQYDVQQG +EwJiajELMAkGA1UECAwCYmoxCzAJBgNVBAcMAmJqMQswCQYDVQQKDAJiajELMAkG +A1UECwwCYmoxCzAJBgNVBAMMAmJqMREwDwYJKoZIhvcNAQkBFgJiajCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMdirlGcu/slwTkgeLczPnKntk0h0ckB +gyAw19nhe2EPdF1d0d4goU7f4+e7rHj4IgnmRmAm/hwqxwY7toO59OH+rBCxpYqJ +jQpARL4qGHID7Gqv/vt6cNc4O0gy0Ul5asY35IAB4qu+eF2XbzUCsrMl1N+nLdPF +av0UJJPIf2v+X8fvILkcByYhCihSj6CPtsfJhlcwquDTRSXQyyE+gpYVQP7FCWbu +LP4b8LVZQl6XppTSQO46VibJD86ninWlhntSmksEkCxNADi9uBkQzTytl/M6roZK +LkW4B1UlSFqOGeIoHkuPi1UItIeh+muRE/kyJ4uDr2KRWoCmMRyEwi0CAwEAAaNT +MFEwHQYDVR0OBBYEFNbkcjD0LSchP5bhKyYgYD4N0oQCMB8GA1UdIwQYMBaAFNbk +cjD0LSchP5bhKyYgYD4N0oQCMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBABHgP91HOYe2FpMnIaNRIpL3jAy2G2E7l3YmNfjJ3l4ZUzqgmIDYVCtt +T/sKyhaDfz6xYoSiFvnQHJc3adKxBl12YhxNXjEjzV1SKhBxChrnAU4TRYd81V2Q +RDimeTWs9w1slwmdeITuH5eJppCsOVWVd6ziT0YCdstWqy0oSJ5G54C5Vhx/2Bsm +IC45lsWBhTYezKPQqbyd39Typ7JLnHQPb4SMVu7SrJotfh9OrCpyrwlRWrepH3DB +M/7boB5nIO9UUZIHAgwoDsycvoyypko420yp4/I30y9ao2zFd89VJ89Fvg0daieS +eKmMDd5Kr+ObtHEt6+eM9i1b7SIBzQw= +-----END CERTIFICATE----- diff --git a/16.secure/config/server.key b/16.secure/config/server.key new file mode 100644 index 0000000..3b406d4 --- /dev/null +++ b/16.secure/config/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAx2KuUZy7+yXBOSB4tzM+cqe2TSHRyQGDIDDX2eF7YQ90XV3R +3iChTt/j57usePgiCeZGYCb+HCrHBju2g7n04f6sELGliomNCkBEvioYcgPsaq/+ ++3pw1zg7SDLRSXlqxjfkgAHiq754XZdvNQKysyXU36ct08Vq/RQkk8h/a/5fx+8g +uRwHJiEKKFKPoI+2x8mGVzCq4NNFJdDLIT6ClhVA/sUJZu4s/hvwtVlCXpemlNJA +7jpWJskPzqeKdaWGe1KaSwSQLE0AOL24GRDNPK2X8zquhkouRbgHVSVIWo4Z4ige +S4+LVQi0h6H6a5ET+TIni4OvYpFagKYxHITCLQIDAQABAoIBABZ6DWSt4pMhYnLq +MjGU2UlblrWDRn7+aKXMW8j4HkyGzXtpBCuiSgz2OF77TSol7tBm4TAdtS+/ssgF +G0ImSHDhoMwQ+rRvZTLuo9E8NZ1Ey/YK+ReoaegZMrWk5Or/gQXvbxbvH3p08mS6 +mZLERxji/uhnlEb3TmRRTP5L7/FgfJWLzUxR/5XOzZmklI2O1O0RIoaLM1IsHu7o +uTZVHa2R/ts+drQs1/hIHR/BGQZwIRF8n00+qQVDsdYQCsW6DNiLRmwGo/MDRna6 +33eZ3kaTJRbwQarVhsWfd3nOzh73gww0tGgHgTR0yDxgzJH7uYW2yc+ZHfAPOhrj +52xWnZECgYEA9bVsROC49NKSwxWrGSOebREIheOFVDyvSvKyLpMOHtPTZCHZmRDW +SYvdSV9B9iC22t/pdQ5OqSHqG/LJJOa+sPs9SOEox4N7/UZNtFKpoldqbjeMy6WH +7IaLuPl7wRwOyzpCiZ7+jYIeTY7SNOw1x3Dijh8fgo1R4fhmcHotFNsCgYEAz7yQ +V+QN1Xx/4ieB9sKuZSkrL5aJl+Asz0Qt/gaxbcik8v8b4eZBml0JGqifGhVdsbtd +eRBdHQNSTTJuPOLd385AUiF4i3ppSQK/FLzLCQ+gLWc5KVHjDNRwvOtQjCwlJTkp +ViN9rtJK2JYXHoXcWwkzerWcZRCrFyjSCYfv75cCgYEAz6PEXiSmSMaWneP21mTC +YhsN38+ZAcnSvPyB1VgSi7yonKr6bx7KaBaZJ4Mng+67eBXW/UPc95MgewPeNaAF +sBxw+uDEDG6x3iSGUAe3MOi8mW26PvKg/iHpe6Thjxy958JRLmm9Zip6n0I9o9ml +zOg5nK7yeuogM10ufIjTBhcCgYBFRm2gUbXnTqha46/seUmtBIiZSwtBcYmf6O2p +e9Ppd3LCch57O8z+zC3ADSFZkmx3W7M1LybOCRCGG941QbaZ7u72NKE9ain4JglQ +whC3SdWxrm2agOtFmQariZGH3STZ//Dv/8/m38wD5DF7hUpRtYTMVAn+jgtwIrXA +Zeu2qwKBgEIEKHaCOiYL2rfenmnDkq+fV9T7l5HksK8KkIdrdaRIFwes5K8WgUOb +/bUq1f8b4wVQu8pApulgq+x0B7S71aFsUjjOuk4KvqDpd4FLgceJL5bCGppwngwC +UIU+bUdPRP1fwmV+AasIovbYzNjsjLgMd9vj3T1PNJinGz4UFdES +-----END RSA PRIVATE KEY----- diff --git a/16.secure/go.mod b/16.secure/go.mod new file mode 100644 index 0000000..36def46 --- /dev/null +++ b/16.secure/go.mod @@ -0,0 +1,8 @@ +module gf-secure + +go 1.14 + +require ( + github.com/gogf/gf v1.12.1 + golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 +) diff --git a/16.secure/main.go b/16.secure/main.go new file mode 100644 index 0000000..d017b73 --- /dev/null +++ b/16.secure/main.go @@ -0,0 +1,163 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/os/glog" + "github.com/gogf/gf/os/gsession" + "github.com/gogf/gf/util/gconv" + "github.com/gogf/gf/util/gvalid" + "golang.org/x/crypto/bcrypt" +) + +const SessionUser = "SessionUser" + +func main() { + s := g.Server() + + // 设置存储方式 + sessionStorage := g.Config().GetString("SessionStorage") + if sessionStorage == "redis" { + s.SetSessionStorage(gsession.NewStorageRedis(g.Redis())) + s.SetSessionIdName(g.Config().GetString("server.SessionIdName")) + } else if sessionStorage == "memory" { + s.SetSessionStorage(gsession.NewStorageMemory()) + } + + // 常规注册 + group := s.Group("/") + group.GET("/", func(r *ghttp.Request) { + r.Response.WriteTpl("index.html", g.Map{ + "title": "登录页面", + }) + }) + + // 用户对象 + type User struct { + Username string `gvalid:"username @required|length:5,16#请输入用户名称|用户名称长度非法"` + Password string `gvalid:"password @required|length:31,33#请输入密码|密码长度非法"` + } + + group.POST("/login", func(r *ghttp.Request) { + username := r.GetString("username") + password := r.GetString("password") + + // 使用结构体定义的校验规则和错误提示进行校验 + if e := gvalid.CheckStruct(User{username, password}, nil); e != nil { + r.Response.WriteJson(g.Map{ + "code": -1, + "msg": e.Error(), + }) + r.Exit() + } + + record, err := g.DB().Table("sys_user").Where("login_name = ? ", username).One() + // 查询数据库异常 + if err != nil { + glog.Error("查询数据错误", err) + r.Response.WriteJson(g.Map{ + "code": -1, + "msg": "查询失败", + }) + r.Exit() + } + // 帐号信息错误 + if record == nil { + r.Response.WriteJson(g.Map{ + "code": -1, + "msg": "帐号信息错误", + }) + r.Exit() + } + + // 直接存入前端传输的 + successPwd := record["password"].String() + comparePwd := password + + // 加盐密码 + // salt := "123456" + // comparePwd, _ = gmd5.EncryptString(comparePwd + salt) + + // bcrypt验证 + err = bcrypt.CompareHashAndPassword([]byte(successPwd), []byte(comparePwd)) + + //if comparePwd == successPwd { + if err == nil { + // 添加session + r.Session.Set(SessionUser, g.Map{ + "username": username, + "realName": record["real_name"].String(), + }) + r.Response.WriteJson(g.Map{ + "code": 0, + "msg": "登录成功", + }) + r.Exit() + } + + r.Response.WriteJson(g.Map{ + "code": -1, + "msg": "登录失败", + }) + }) + + // 用户组 + userGroup := s.Group("/user") + userGroup.Middleware(MiddlewareAuth) + // 列表页面 + userGroup.GET("/index", func(r *ghttp.Request) { + realName := gconv.String(r.Session.GetMap(SessionUser)["realName"]) + r.Response.WriteTpl("user_index.html", g.Map{ + "title": "用户信息列表页面", + "realName": realName, + "dataList": g.List{ + g.Map{ + "date": "2020-04-01", + "name": "朱元璋", + "address": "江苏110号", + }, + g.Map{ + "date": "2020-04-02", + "name": "徐达", + "address": "江苏111号", + }, + g.Map{ + "date": "2020-04-03", + "name": "李善长", + "address": "江苏112号", + }, + }}) + }) + userGroup.POST("/logout", func(r *ghttp.Request) { + // 删除session + r.Session.Remove(SessionUser) + + r.Response.WriteJson(g.Map{ + "code": 0, + "msg": "登出成功", + }) + }) + + // 生成秘钥文件 + // openssl genrsa -out server.key 2048 + // 生成证书文件 + // openssl req -new -x509 -key server.key -out server.crt -days 365 + s.EnableHTTPS("config/server.crt", "config/server.key") + s.SetHTTPSPort(8080) + s.SetPort(8199) + + s.Run() +} + +// 认证中间件 +func MiddlewareAuth(r *ghttp.Request) { + if r.Session.Contains(SessionUser) { + r.Middleware.Next() + } else { + // 获取用错误码 + r.Response.WriteJson(g.Map{ + "code": 403, + "msg": "您访问超时或已登出", + }) + } +} diff --git a/16.secure/public/md5.js b/16.secure/public/md5.js new file mode 100644 index 0000000..f90cb72 --- /dev/null +++ b/16.secure/public/md5.js @@ -0,0 +1,256 @@ +/* + * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message + * Digest Algorithm, as defined in RFC 1321. + * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for more info. + */ + +/* + * Configurable variables. You may need to tweak these to be compatible with + * the server-side, but the defaults work in most cases. + */ +var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ +var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */ +var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ + +/* + * These are the functions you'll usually want to call + * They take string arguments and return either hex or base-64 encoded strings + */ +function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));} +function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));} +function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));} +function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); } +function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); } +function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); } + +/* + * Perform a simple self-test to see if the VM is working + */ +function md5_vm_test() +{ + return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72"; +} + +/* + * Calculate the MD5 of an array of little-endian words, and a bit length + */ +function core_md5(x, len) +{ + /* append padding */ + x[len >> 5] |= 0x80 << ((len) % 32); + x[(((len + 64) >>> 9) << 4) + 14] = len; + + var a = 1732584193; + var b = -271733879; + var c = -1732584194; + var d = 271733878; + + for(var i = 0; i < x.length; i += 16) + { + var olda = a; + var oldb = b; + var oldc = c; + var oldd = d; + + a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); + d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); + c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); + b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); + a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); + d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); + c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); + b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); + a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); + d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); + c = md5_ff(c, d, a, b, x[i+10], 17, -42063); + b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); + a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); + d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); + c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); + b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); + + a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); + d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); + c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); + b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); + a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); + d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); + c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); + b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); + a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); + d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); + c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); + b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); + a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); + d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); + c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); + b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); + + a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); + d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); + c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); + b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); + a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); + d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); + c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); + b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); + a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); + d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); + c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); + b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); + a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); + d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); + c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); + b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); + + a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); + d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); + c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); + b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); + a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); + d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); + c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); + b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); + a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); + d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); + c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); + b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); + a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); + d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); + c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); + b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); + + a = safe_add(a, olda); + b = safe_add(b, oldb); + c = safe_add(c, oldc); + d = safe_add(d, oldd); + } + return Array(a, b, c, d); + +} + +/* + * These functions implement the four basic operations the algorithm uses. + */ +function md5_cmn(q, a, b, x, s, t) +{ + return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b); +} +function md5_ff(a, b, c, d, x, s, t) +{ + return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); +} +function md5_gg(a, b, c, d, x, s, t) +{ + return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); +} +function md5_hh(a, b, c, d, x, s, t) +{ + return md5_cmn(b ^ c ^ d, a, b, x, s, t); +} +function md5_ii(a, b, c, d, x, s, t) +{ + return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); +} + +/* + * Calculate the HMAC-MD5, of a key and some data + */ +function core_hmac_md5(key, data) +{ + var bkey = str2binl(key); + if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz); + + var ipad = Array(16), opad = Array(16); + for(var i = 0; i < 16; i++) + { + ipad[i] = bkey[i] ^ 0x36363636; + opad[i] = bkey[i] ^ 0x5C5C5C5C; + } + + var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz); + return core_md5(opad.concat(hash), 512 + 128); +} + +/* + * Add integers, wrapping at 2^32. This uses 16-bit operations internally + * to work around bugs in some JS interpreters. + */ +function safe_add(x, y) +{ + var lsw = (x & 0xFFFF) + (y & 0xFFFF); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); +} + +/* + * Bitwise rotate a 32-bit number to the left. + */ +function bit_rol(num, cnt) +{ + return (num << cnt) | (num >>> (32 - cnt)); +} + +/* + * Convert a string to an array of little-endian words + * If chrsz is ASCII, characters >255 have their hi-byte silently ignored. + */ +function str2binl(str) +{ + var bin = Array(); + var mask = (1 << chrsz) - 1; + for(var i = 0; i < str.length * chrsz; i += chrsz) + bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32); + return bin; +} + +/* + * Convert an array of little-endian words to a string + */ +function binl2str(bin) +{ + var str = ""; + var mask = (1 << chrsz) - 1; + for(var i = 0; i < bin.length * 32; i += chrsz) + str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask); + return str; +} + +/* + * Convert an array of little-endian words to a hex string. + */ +function binl2hex(binarray) +{ + var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; + var str = ""; + for(var i = 0; i < binarray.length * 4; i++) + { + str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) + + hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF); + } + return str; +} + +/* + * Convert an array of little-endian words to a base-64 string + */ +function binl2b64(binarray) +{ + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var str = ""; + for(var i = 0; i < binarray.length * 4; i += 3) + { + var triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16) + | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 ) + | ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF); + for(var j = 0; j < 4; j++) + { + if(i * 8 + j * 6 > binarray.length * 32) str += b64pad; + else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); + } + } + return str; +} \ No newline at end of file diff --git a/16.secure/sql/init.sql b/16.secure/sql/init.sql new file mode 100644 index 0000000..eb2df64 --- /dev/null +++ b/16.secure/sql/init.sql @@ -0,0 +1,29 @@ +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for sys_user +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user`; +CREATE TABLE `sys_user` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', + `uuid` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'UUID', + `login_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '登录名/11111', + `password` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码', + `real_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '真实姓名', + `enable` tinyint(1) NULL DEFAULT 1 COMMENT '是否启用//radio/1,启用,2,禁用', + `update_time` varchar(24) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新时间', + `update_id` int(11) NULL DEFAULT 0 COMMENT '更新人', + `create_time` varchar(24) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `create_id` int(11) NULL DEFAULT 0 COMMENT '创建者', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uni_user_username`(`login_name`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 23 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_user +-- ---------------------------- +INSERT INTO `sys_user` VALUES (1, '94091b1fa6ac4a27a06c0b92155aea6a', 'admin', '$2a$10$RAQhfHSilINka4OGQRoyqODPqK7qihpwKzfH1UPu7iq0de3wBsCZS', '系统管理员', 1, '2019-12-24 12:01:43', 1, '2017-03-19 20:41:25', 1); +INSERT INTO `sys_user` VALUES (2, '84091b1fa6ac4a27a06c0b92155aea6b', 'test', '$2a$10$6abcq8HdFMUm4Qr6sOBasOOQYotyaOvKfl951I/HdMN9br5q3BGK6', '测试用户', 1, '2019-12-24 12:01:43', 1, '2017-03-19 20:41:25', 1); + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/16.secure/template/index.html b/16.secure/template/index.html new file mode 100644 index 0000000..458bf90 --- /dev/null +++ b/16.secure/template/index.html @@ -0,0 +1,79 @@ + + + + + + + + + +
+ + + ${ .title } + + + + + + + + + + + + + + + 登录 + + + + +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/16.secure/template/user_index.html b/16.secure/template/user_index.html new file mode 100644 index 0000000..27bbda5 --- /dev/null +++ b/16.secure/template/user_index.html @@ -0,0 +1,105 @@ + + + + + + + + + +
+ + + 欢迎【${.realName }】访问 ${ .title } + + + + + + + + + + 登出 + + + + +
+ + + + + + + + + + diff --git a/16.secure/test/test.http b/16.secure/test/test.http new file mode 100644 index 0000000..04a6534 --- /dev/null +++ b/16.secure/test/test.http @@ -0,0 +1,8 @@ +POST http://127.0.0.1/user/index +Cookie: gSessionId=C1YKVY1S9PUKMPBQQB + +### + +GET https://127.0.0.1/user/index + +### \ No newline at end of file diff --git a/17.gfcli/.gitattributes b/17.gfcli/.gitattributes new file mode 100644 index 0000000..8d6f5cf --- /dev/null +++ b/17.gfcli/.gitattributes @@ -0,0 +1,3 @@ +*.js linguist-language=GO +*.css linguist-language=GO +*.html linguist-language=GO \ No newline at end of file diff --git a/17.gfcli/.gitignore b/17.gfcli/.gitignore new file mode 100644 index 0000000..a1a1fa4 --- /dev/null +++ b/17.gfcli/.gitignore @@ -0,0 +1,19 @@ +.buildpath +.hgignore.swp +.project +.orig +.swp +.idea/ +.settings/ +.vscode/ +vender/ +log/ +composer.lock +gitpush.sh +pkg/ +bin/ +cbuild +*/.DS_Store +main +.vscode +go.sum \ No newline at end of file diff --git a/17.gfcli/Dockerfile b/17.gfcli/Dockerfile new file mode 100644 index 0000000..fd6d44c --- /dev/null +++ b/17.gfcli/Dockerfile @@ -0,0 +1,31 @@ +FROM loads/alpine:3.8 + +LABEL maintainer="john@goframe.org" + +############################################################################### +# INSTALLATION +############################################################################### + +# 设置固定的项目路径 +ENV WORKDIR /var/www/gfcli + +# 添加应用可执行文件,并设置执行权限 +ADD ./bin/linux_amd64/main $WORKDIR/main +RUN chmod +x $WORKDIR/main + +# 添加I18N多语言文件、静态文件、配置文件、模板文件 +ADD i18n $WORKDIR/i18n +ADD public $WORKDIR/public +ADD config $WORKDIR/config +ADD template $WORKDIR/template + +############################################################################### +# START +############################################################################### +# PORT +EXPOSE 8199 + +WORKDIR $WORKDIR +CMD ./main + + diff --git a/17.gfcli/README.MD b/17.gfcli/README.MD new file mode 100644 index 0000000..320d20c --- /dev/null +++ b/17.gfcli/README.MD @@ -0,0 +1,3 @@ +# GoFrame Project + +https://goframe.org diff --git a/17.gfcli/app/api/hello/hello.go b/17.gfcli/app/api/hello/hello.go new file mode 100644 index 0000000..c60e74e --- /dev/null +++ b/17.gfcli/app/api/hello/hello.go @@ -0,0 +1,20 @@ +package hello + +import ( + "gfcli/app/model/user" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/os/glog" +) + +// Hello is a demonstration route handler for output "Hello World!". +func Hello(r *ghttp.Request) { + r.Response.Writeln("Hello World!") + entity, err := user.FindOne("login_name = ?", "admin") + if err != nil { + glog.Error(err) + r.Response.Writeln("err") + r.Exit() + } + r.Response.Writeln(entity.Id) + r.Response.Writeln(entity) +} diff --git a/17.gfcli/app/model/.gitkeep b/17.gfcli/app/model/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/17.gfcli/app/model/user/user.go b/17.gfcli/app/model/user/user.go new file mode 100644 index 0000000..5b26d51 --- /dev/null +++ b/17.gfcli/app/model/user/user.go @@ -0,0 +1,7 @@ +// ============================================================================ +// This is auto-generated by gf cli tool only once. Fill this file as you wish. +// ============================================================================ + +package user + +// Fill with you ideas below. diff --git a/17.gfcli/app/model/user/user_entity.go b/17.gfcli/app/model/user/user_entity.go new file mode 100644 index 0000000..0a4fb1e --- /dev/null +++ b/17.gfcli/app/model/user/user_entity.go @@ -0,0 +1,66 @@ +// ========================================================================== +// This is auto-generated by gf cli tool. You may not really want to edit it. +// ========================================================================== + +package user + +import ( + "database/sql" + "github.com/gogf/gf/database/gdb" +) + +// Entity is the golang structure for table sys_user. +type Entity struct { + Id int `orm:"id,primary" json:"id"` // 主键 + Uuid string `orm:"uuid" json:"uuid"` // UUID + LoginName string `orm:"login_name,unique" json:"login_name"` // 登录名/11111 + Password string `orm:"password" json:"password"` // 密码 + RealName string `orm:"real_name" json:"real_name"` // 真实姓名 + Enable int `orm:"enable" json:"enable"` // 是否启用//radio/1,启用,2,禁用 + UpdateTime string `orm:"update_time" json:"update_time"` // 更新时间 + UpdateId int `orm:"update_id" json:"update_id"` // 更新人 + CreateTime string `orm:"create_time" json:"create_time"` // 创建时间 + CreateId int `orm:"create_id" json:"create_id"` // 创建者 +} + +// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers +// the data and where attributes for empty values. +func (r *Entity) OmitEmpty() *arModel { + return Model.Data(r).OmitEmpty() +} + +// Inserts does "INSERT...INTO..." statement for inserting current object into table. +func (r *Entity) Insert() (result sql.Result, err error) { + return Model.Data(r).Insert() +} + +// InsertIgnore does "INSERT IGNORE INTO ..." statement for inserting current object into table. +func (r *Entity) InsertIgnore() (result sql.Result, err error) { + return Model.Data(r).InsertIgnore() +} + +// Replace does "REPLACE...INTO..." statement for inserting current object into table. +// If there's already another same record in the table (it checks using primary key or unique index), +// it deletes it and insert this one. +func (r *Entity) Replace() (result sql.Result, err error) { + return Model.Data(r).Replace() +} + +// Save does "INSERT...INTO..." statement for inserting/updating current object into table. +// It updates the record if there's already another same record in the table +// (it checks using primary key or unique index). +func (r *Entity) Save() (result sql.Result, err error) { + return Model.Data(r).Save() +} + +// Update does "UPDATE...WHERE..." statement for updating current object from table. +// It updates the record if there's already another same record in the table +// (it checks using primary key or unique index). +func (r *Entity) Update() (result sql.Result, err error) { + return Model.Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update() +} + +// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table. +func (r *Entity) Delete() (result sql.Result, err error) { + return Model.Where(gdb.GetWhereConditionOfStruct(r)).Delete() +} diff --git a/17.gfcli/app/model/user/user_model.go b/17.gfcli/app/model/user/user_model.go new file mode 100644 index 0000000..07f64f4 --- /dev/null +++ b/17.gfcli/app/model/user/user_model.go @@ -0,0 +1,344 @@ +// ========================================================================== +// This is auto-generated by gf cli tool. You may not really want to edit it. +// ========================================================================== + +package user + +import ( + "database/sql" + "github.com/gogf/gf/database/gdb" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/frame/gmvc" + "time" +) + +// arModel is a active record design model for table sys_user operations. +type arModel struct { + gmvc.M +} + +var ( + // Table is the table name of sys_user. + Table = "sys_user" + // Model is the model object of sys_user. + Model = &arModel{g.DB("default").Table(Table).Safe()} + // Columns defines and stores column names for table sys_user. + Columns = struct { + Id string // 主键 + Uuid string // UUID + LoginName string // 登录名/11111 + Password string // 密码 + RealName string // 真实姓名 + Enable string // 是否启用//radio/1,启用,2,禁用 + UpdateTime string // 更新时间 + UpdateId string // 更新人 + CreateTime string // 创建时间 + CreateId string // 创建者 + }{ + Id: "id", + Uuid: "uuid", + LoginName: "login_name", + Password: "password", + RealName: "real_name", + Enable: "enable", + UpdateTime: "update_time", + UpdateId: "update_id", + CreateTime: "create_time", + CreateId: "create_id", + } +) + +// FindOne is a convenience method for Model.FindOne. +// See Model.FindOne. +func FindOne(where ...interface{}) (*Entity, error) { + return Model.FindOne(where...) +} + +// FindAll is a convenience method for Model.FindAll. +// See Model.FindAll. +func FindAll(where ...interface{}) ([]*Entity, error) { + return Model.FindAll(where...) +} + +// FindValue is a convenience method for Model.FindValue. +// See Model.FindValue. +func FindValue(fieldsAndWhere ...interface{}) (gdb.Value, error) { + return Model.FindValue(fieldsAndWhere...) +} + +// FindArray is a convenience method for Model.FindArray. +// See Model.FindArray. +func FindArray(fieldsAndWhere ...interface{}) ([]gdb.Value, error) { + return Model.FindArray(fieldsAndWhere...) +} + +// FindCount is a convenience method for Model.FindCount. +// See Model.FindCount. +func FindCount(where ...interface{}) (int, error) { + return Model.FindCount(where...) +} + +// Insert is a convenience method for Model.Insert. +func Insert(data ...interface{}) (result sql.Result, err error) { + return Model.Insert(data...) +} + +// InsertIgnore is a convenience method for Model.InsertIgnore. +func InsertIgnore(data ...interface{}) (result sql.Result, err error) { + return Model.InsertIgnore(data...) +} + +// Replace is a convenience method for Model.Replace. +func Replace(data ...interface{}) (result sql.Result, err error) { + return Model.Replace(data...) +} + +// Save is a convenience method for Model.Save. +func Save(data ...interface{}) (result sql.Result, err error) { + return Model.Save(data...) +} + +// Update is a convenience method for Model.Update. +func Update(dataAndWhere ...interface{}) (result sql.Result, err error) { + return Model.Update(dataAndWhere...) +} + +// Delete is a convenience method for Model.Delete. +func Delete(where ...interface{}) (result sql.Result, err error) { + return Model.Delete(where...) +} + +// As sets an alias name for current table. +func (m *arModel) As(as string) *arModel { + return &arModel{m.M.As(as)} +} + +// TX sets the transaction for current operation. +func (m *arModel) TX(tx *gdb.TX) *arModel { + return &arModel{m.M.TX(tx)} +} + +// Master marks the following operation on master node. +func (m *arModel) Master() *arModel { + return &arModel{m.M.Master()} +} + +// Slave marks the following operation on slave node. +// Note that it makes sense only if there's any slave node configured. +func (m *arModel) Slave() *arModel { + return &arModel{m.M.Slave()} +} + +// LeftJoin does "LEFT JOIN ... ON ..." statement on the model. +func (m *arModel) LeftJoin(joinTable string, on string) *arModel { + return &arModel{m.M.LeftJoin(joinTable, on)} +} + +// RightJoin does "RIGHT JOIN ... ON ..." statement on the model. +func (m *arModel) RightJoin(joinTable string, on string) *arModel { + return &arModel{m.M.RightJoin(joinTable, on)} +} + +// InnerJoin does "INNER JOIN ... ON ..." statement on the model. +func (m *arModel) InnerJoin(joinTable string, on string) *arModel { + return &arModel{m.M.InnerJoin(joinTable, on)} +} + +// Fields sets the operation fields of the model, multiple fields joined using char ','. +func (m *arModel) Fields(fields string) *arModel { + return &arModel{m.M.Fields(fields)} +} + +// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','. +func (m *arModel) FieldsEx(fields string) *arModel { + return &arModel{m.M.FieldsEx(fields)} +} + +// Option sets the extra operation option for the model. +func (m *arModel) Option(option int) *arModel { + return &arModel{m.M.Option(option)} +} + +// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers +// the data and where attributes for empty values. +func (m *arModel) OmitEmpty() *arModel { + return &arModel{m.M.OmitEmpty()} +} + +// Filter marks filtering the fields which does not exist in the fields of the operated table. +func (m *arModel) Filter() *arModel { + return &arModel{m.M.Filter()} +} + +// Where sets the condition statement for the model. The parameter can be type of +// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times, +// multiple conditions will be joined into where statement using "AND". +// Eg: +// Where("uid=10000") +// Where("uid", 10000) +// Where("money>? AND name like ?", 99999, "vip_%") +// Where("uid", 1).Where("name", "john") +// Where("status IN (?)", g.Slice{1,2,3}) +// Where("age IN(?,?)", 18, 50) +// Where(User{ Id : 1, UserName : "john"}) +func (m *arModel) Where(where interface{}, args ...interface{}) *arModel { + return &arModel{m.M.Where(where, args...)} +} + +// And adds "AND" condition to the where statement. +func (m *arModel) And(where interface{}, args ...interface{}) *arModel { + return &arModel{m.M.And(where, args...)} +} + +// Or adds "OR" condition to the where statement. +func (m *arModel) Or(where interface{}, args ...interface{}) *arModel { + return &arModel{m.M.Or(where, args...)} +} + +// Group sets the "GROUP BY" statement for the model. +func (m *arModel) Group(groupBy string) *arModel { + return &arModel{m.M.Group(groupBy)} +} + +// Order sets the "ORDER BY" statement for the model. +func (m *arModel) Order(orderBy string) *arModel { + return &arModel{m.M.Order(orderBy)} +} + +// Limit sets the "LIMIT" statement for the model. +// The parameter can be either one or two number, if passed two number is passed, +// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]" +// statement. +func (m *arModel) Limit(limit ...int) *arModel { + return &arModel{m.M.Limit(limit...)} +} + +// Offset sets the "OFFSET" statement for the model. +// It only makes sense for some databases like SQLServer, PostgreSQL, etc. +func (m *arModel) Offset(offset int) *arModel { + return &arModel{m.M.Offset(offset)} +} + +// Page sets the paging number for the model. +// The parameter is started from 1 for paging. +// Note that, it differs that the Limit function start from 0 for "LIMIT" statement. +func (m *arModel) Page(page, limit int) *arModel { + return &arModel{m.M.Page(page, limit)} +} + +// Batch sets the batch operation number for the model. +func (m *arModel) Batch(batch int) *arModel { + return &arModel{m.M.Batch(batch)} +} + +// Cache sets the cache feature for the model. It caches the result of the sql, which means +// if there's another same sql request, it just reads and returns the result from cache, it +// but not committed and executed into the database. +// +// If the parameter < 0, which means it clear the cache with given . +// If the parameter = 0, which means it never expires. +// If the parameter > 0, which means it expires after . +// +// The optional parameter is used to bind a name to the cache, which means you can later +// control the cache like changing the or clearing the cache with specified . +// +// Note that, the cache feature is disabled if the model is operating on a transaction. +func (m *arModel) Cache(duration time.Duration, name ...string) *arModel { + return &arModel{m.M.Cache(duration, name...)} +} + +// Data sets the operation data for the model. +// The parameter can be type of string/map/gmap/slice/struct/*struct, etc. +// Eg: +// Data("uid=10000") +// Data("uid", 10000) +// Data(g.Map{"uid": 10000, "name":"john"}) +// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) +func (m *arModel) Data(data ...interface{}) *arModel { + return &arModel{m.M.Data(data...)} +} + +// All does "SELECT FROM ..." statement for the model. +// It retrieves the records from table and returns the result as []*Entity. +// It returns nil if there's no record retrieved with the given conditions from table. +// +// The optional parameter is the same as the parameter of Model.Where function, +// see Model.Where. +func (m *arModel) All(where ...interface{}) ([]*Entity, error) { + all, err := m.M.All(where...) + if err != nil { + return nil, err + } + var entities []*Entity + if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows { + return nil, err + } + return entities, nil +} + +// One retrieves one record from table and returns the result as *Entity. +// It returns nil if there's no record retrieved with the given conditions from table. +// +// The optional parameter is the same as the parameter of Model.Where function, +// see Model.Where. +func (m *arModel) One(where ...interface{}) (*Entity, error) { + one, err := m.M.One(where...) + if err != nil { + return nil, err + } + var entity *Entity + if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows { + return nil, err + } + return entity, nil +} + +// FindOne retrieves and returns a single Record by Model.WherePri and Model.One. +// Also see Model.WherePri and Model.One. +func (m *arModel) FindOne(where ...interface{}) (*Entity, error) { + one, err := m.M.FindOne(where...) + if err != nil { + return nil, err + } + var entity *Entity + if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows { + return nil, err + } + return entity, nil +} + +// FindAll retrieves and returns Result by by Model.WherePri and Model.All. +// Also see Model.WherePri and Model.All. +func (m *arModel) FindAll(where ...interface{}) ([]*Entity, error) { + all, err := m.M.FindAll(where...) + if err != nil { + return nil, err + } + var entities []*Entity + if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows { + return nil, err + } + return entities, nil +} + +// Chunk iterates the table with given size and callback function. +func (m *arModel) Chunk(limit int, callback func(entities []*Entity, err error) bool) { + m.M.Chunk(limit, func(result gdb.Result, err error) bool { + var entities []*Entity + err = result.Structs(&entities) + if err == sql.ErrNoRows { + return false + } + return callback(entities, err) + }) +} + +// LockUpdate sets the lock for update for current operation. +func (m *arModel) LockUpdate() *arModel { + return &arModel{m.M.LockUpdate()} +} + +// LockShared sets the lock in share mode for current operation. +func (m *arModel) LockShared() *arModel { + return &arModel{m.M.LockShared()} +} diff --git a/17.gfcli/app/service/.gitkeep b/17.gfcli/app/service/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/17.gfcli/app/service/user/userSvc.go b/17.gfcli/app/service/user/userSvc.go new file mode 100644 index 0000000..e9a6e93 --- /dev/null +++ b/17.gfcli/app/service/user/userSvc.go @@ -0,0 +1,89 @@ +package user + +import ( + "errors" + "gfcli/app/model/user" + "github.com/gogf/gf/os/glog" + "github.com/gogf/gf/util/gconv" +) + +// 请求参数 +type Request struct { + user.Entity +} + +// 通过id获取实体 +func GetById(id int64) (*user.Entity, error) { + if id <= 0 { + glog.Error(" get id error") + return nil, errors.New("参数不合法") + } + + return user.Model.One(" id = ?", id) +} + +// 删除实体 +func Delete(id int64) (int64, error) { + if id <= 0 { + glog.Error("delete id error") + return 0, errors.New("参数不合法") + } + + // 获取删除对象 + r, err1 := user.Model.Delete(" id = ?", id) + if err1 != nil { + return 0, err1 + } + + return r.RowsAffected() +} + +// 保存实体 +func Save(request *Request) (int64, error) { + entity := (*user.Entity)(nil) + err := gconv.StructDeep(request.Entity, &entity) + if err != nil { + return 0, errors.New("数据错误") + } + + // 判断新增还是修改 + if entity.Id <= 0 { + + r, err := user.Model.Insert(entity) + if err != nil { + return 0, err + } + + return r.RowsAffected() + } else { + r, err := user.Model.OmitEmpty().Where(" id = ?", entity.Id).Update(entity) + if err != nil { + return 0, err + } + + return r.RowsAffected() + } +} + +// 分页查询 +func Page(page, limit int) ([]*user.Entity, int, error) { + if page <= 0 || limit <= 0 { + glog.Error("page param error", form.Page, form.Rows) + return nil, 0, nil + } + + num, err := user.Model.As("t").FindCount() + + if err != nil { + glog.Error("page count error", err) + return nil, 0, err + } + + dbModel, err := user.Model.As("t").Page(page, limit).All() + if err != nil { + glog.Error("page list error", err) + return nil, 0, err + } + + return dbModel, num, nil +} diff --git a/17.gfcli/boot/.gitkeep b/17.gfcli/boot/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/17.gfcli/boot/boot.go b/17.gfcli/boot/boot.go new file mode 100644 index 0000000..21f17a5 --- /dev/null +++ b/17.gfcli/boot/boot.go @@ -0,0 +1,5 @@ +package boot + +func init() { + +} diff --git a/17.gfcli/boot/data.go b/17.gfcli/boot/data.go new file mode 100644 index 0000000..b9f3533 --- /dev/null +++ b/17.gfcli/boot/data.go @@ -0,0 +1,9 @@ +package boot + +import "github.com/gogf/gf/os/gres" + +func init() { + if err := gres.Add("1f8b08000000000002ffa495df4fd35014c70f63ccad842063f00292a6f8a089740c1065890924047fc0c394f9b48ca46c9752b85b477b0b1aa3893e1a9ff0cdf8c203897f80f1c5575f4de41ff15fd0dc765d7bb77bc7d6358176f79e7ecef79c9cfb6d617b389e8124004c9f7d2940e81a871454ccfa81a16755dd20c708355e16476068e1e272afb07d23198e0d281f3fb0947440f16e2a316bd805fdfa7eb9372f3f29160bf22eb24e9125956cf75e96521bd5aa856c5ba6d72359c93fccadad2952ca8b7b619ac45d6e38fbd8a828526ac7d40b1a39f4c3b1a9db0bfa812249f3f28ea9ebc852a512761fca128d690507f18abbb1834e110e36348cbdf55d52351de2af13cb41945d44b506d60852a5d2a981ce0474d28cf2489be8407330d93230a29b46bd8a5ea987a486fd6d6cd40c822c9bbe2b9794db6f947bb2f25629d3849b1ad1f6359b26ac361fbd94d8a81fbbc96aafed139cb74c93e4734bcb2bf757d749a57127b7f4405d5417d55c3eb7bcbcb87a374b904dbc8455b4efe87e4d7421c822e366efe8722ba11a6e64a85cbfeff649b394503fc3ad0cb5d3cb4a8729fefcfc713c0640ff82617afae92b334c89d630b90334637ceb3289edf33c0a29f006264bdbddc32cb713322ca19f63d18e1a0b500dece8463d0263ba9d3188a0f1006621db74ac0a8a4099eca4642bb61d8134cb270d52e2140769d4343d4aa17322d62002d31ce85194e6cd7041fd48e39d3c8f18e1e44d400a7c131c444432c4e92263289619160fb97ffd784fff8b3f7001857ed2c2fe9466285b0185f7810b83784df53b04eb9f63d0696ee25a465b2fdf847f1b7f622030373121c3d4911e86ebcc4d8c1a63c494035487b98919d38c9c8b76467f82c619417f0318c7dcc4944986b212876bcc4d4c9a658a7bc727f557e21423ee8a836c3737316b8e91776b047a37373134cd08b438d0a3de9a37c3a8fbc9057597d6fde48d25a0d3dcc472261839cf12d0d5dcc422928c88f31027246324e1054cc0ef38c095fbeb7f000000ffff107950e1b40b0000"); err != nil { + panic(err) + } +} diff --git a/17.gfcli/config/.gitkeep b/17.gfcli/config/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/17.gfcli/config/config.toml b/17.gfcli/config/config.toml new file mode 100644 index 0000000..65923c1 --- /dev/null +++ b/17.gfcli/config/config.toml @@ -0,0 +1,27 @@ +# HTTP Server +[server] + Address = ":8199" + ServerRoot = "public" + LogPath = "logs/gf" + +# Logger. +[logger] + Path = "logs" + Level = "all" + Stdout = true + +# Template. +[viewer] + Path = "template" + DefaultFile = "index.html" + Delimiters = ["${", "}"] + +# Database. +[database] + link = "mysql:root:123456@tcp(127.0.0.1:13306)/test" + debug = true + # Database logger. + [database.logger] + Path = "logs/sql" + Level = "all" + Stdout = true \ No newline at end of file diff --git a/17.gfcli/docker/.gitkeep b/17.gfcli/docker/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/17.gfcli/document/.gitkeep b/17.gfcli/document/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/17.gfcli/go.mod b/17.gfcli/go.mod new file mode 100644 index 0000000..d2b8c90 --- /dev/null +++ b/17.gfcli/go.mod @@ -0,0 +1,5 @@ +module gfcli + +require github.com/gogf/gf v1.12.2 + +go 1.13 diff --git a/17.gfcli/i18n/.gitkeep b/17.gfcli/i18n/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/17.gfcli/main.go b/17.gfcli/main.go new file mode 100644 index 0000000..800b73d --- /dev/null +++ b/17.gfcli/main.go @@ -0,0 +1,13 @@ +package main + +import ( + _ "gfcli/boot" + _ "gfcli/router" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/os/gres" +) + +func main() { + gres.Dump() + g.Server().Run() +} diff --git a/17.gfcli/public/html/.gitkeep b/17.gfcli/public/html/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/17.gfcli/public/plugin/.gitkeep b/17.gfcli/public/plugin/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/17.gfcli/public/resource/css/.gitkeep b/17.gfcli/public/resource/css/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/17.gfcli/public/resource/image/.gitkeep b/17.gfcli/public/resource/image/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/17.gfcli/public/resource/js/.gitkeep b/17.gfcli/public/resource/js/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/17.gfcli/router/.gitkeep b/17.gfcli/router/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/17.gfcli/router/router.go b/17.gfcli/router/router.go new file mode 100644 index 0000000..a06af3d --- /dev/null +++ b/17.gfcli/router/router.go @@ -0,0 +1,14 @@ +package router + +import ( + "gfcli/app/api/hello" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +func init() { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.ALL("/", hello.Hello) + }) +} diff --git a/17.gfcli/template/.gitkeep b/17.gfcli/template/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/23.gregex/go.mod b/23.gregex/go.mod new file mode 100644 index 0000000..04db8db --- /dev/null +++ b/23.gregex/go.mod @@ -0,0 +1,5 @@ +module gfregex + +go 1.14 + +require github.com/gogf/gf v1.13.4 diff --git a/23.gregex/test/gregex_test.go b/23.gregex/test/gregex_test.go new file mode 100644 index 0000000..71ec548 --- /dev/null +++ b/23.gregex/test/gregex_test.go @@ -0,0 +1,101 @@ +package test + +import ( + "fmt" + "github.com/gogf/gf/text/gregex" + "testing" +) + +// IsMatch +func TestIsMatch(t *testing.T) { + // 校验时间是否合法 + var pattern = `\d{4}-\d{2}-\d{2}` + s1 := []byte(`2019-07-20`) + fmt.Println("IsMatch1", gregex.IsMatch(pattern, s1)) + pattern = `[21]\d{3}-\d{1,2}-\d{1,2}` + fmt.Println("IsMatch2", gregex.IsMatch(pattern, s1)) +} + +// IsMatchString +func TestIsMatchString(t *testing.T) { + var pattern = `[21]\d{3}-[01]?\d-[0123]?\d` + s1 := `2019-07-20` + fmt.Println("IsMatchString", gregex.IsMatchString(pattern, s1)) +} + +var ( + textStr = "123 xiangyu liubang xiangyu liubang" + patternStr = `\d+\s(\w+)\s\w+\s\w+\s\w+` + patternStr2 = `\d+\s(\w+)` + patternStr3 = `(\w+)\sliubang` +) + +// Match +func TestMatch(t *testing.T) { + subs, err := gregex.Match(patternStr, []byte(textStr)) + if err != nil { + t.Error("Match", err) + } + fmt.Println("Match", string(subs[0]), "##group:", string(subs[1]), err) +} + +// MatchString +func TestMatchString(t *testing.T) { + // 匹配全部内容 + subs, err := gregex.MatchString(patternStr, textStr) + if err != nil { + t.Error("MatchString", err) + } + fmt.Println("MatchString", subs[0], "##group:", subs[1], err) + + // 匹配部分内容 + subs, err = gregex.MatchString(patternStr2, textStr) + if err != nil { + t.Error("MatchString2", err) + } + fmt.Println("MatchString2", subs[0], "##group:", subs[1], err) +} + +// MatchAll +func TestMatchAll(t *testing.T) { + allGroup, err := gregex.MatchAll(patternStr3, []byte(textStr)) + if err != nil { + t.Error("MatchAll", err) + } + fmt.Println("MatchAll", string(allGroup[0][0]), "##group:", string(allGroup[0][1]), err) + fmt.Println("MatchAll", string(allGroup[1][0]), "##group:", string(allGroup[1][1]), err) +} + +// MatchAllString +func TestMatchAllString(t *testing.T) { + allGroup, err := gregex.MatchAllString(patternStr3, textStr) + if err != nil { + t.Error("MatchAllString", err) + } + fmt.Println("MatchAllString", allGroup, "##group:", allGroup[0][1], err) +} + +// Replace +func TestReplace(t *testing.T) { + replace, err := gregex.Replace(patternStr3, []byte("zhuyuanzhang chenyouliang"), []byte(textStr)) + if err != nil { + t.Error("Replace", err) + } + fmt.Println("Replace", string(replace), "##src:", textStr, err) + +} + +// ReplaceString +func TestReplaceString(t *testing.T) { + replacedStr, err := gregex.ReplaceString(patternStr3, "zhuyuanzhang chenyouliang", textStr) + if err != nil { + t.Error("ReplaceString", err) + } + fmt.Println("ReplaceString", replacedStr, "##src:", textStr, err) +} + +// Split +func TestSplit(t *testing.T) { + items := gregex.Split(`\sxiangyu\s`, textStr) + fmt.Println("Split", items, "###0:", items[0], "##src:", textStr) +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..569ac31 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2020] [FLY的狐狸] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 0000000..648ba32 --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,100 @@ +# GoFrame教程 + +## 一、GoFrame基础教程-快速入门 + +### 内容介绍 + +GoFrame基本介绍,安装和部署,web项目,路由注册、http 客户端,配置文件、日志打印、Mysql数据库连接、Redis连接,常用工具gstr,gmap,gjson,md5,gconv + +### 目录结构 + +* [01.GoFrame介绍.md](doc_basic/01.goframe介绍.md) +* [02.GoFrame基础环境搭建.md](doc_basic/02.goframe基础环境搭建.md) +* [03.GoFrame的WEB服务介绍.md](doc_basic/03.goframe的WEB服务介绍.md) +* [04.GoFrame路由注册.md](doc_basic/04.goframe路由注册.md) +* [05.GoFrame的HTTP客户端.md](doc_basic/05.goframe的HTTP客户端.md) +* [06.GoFrame配置文件.md](doc_basic/06.goframe配置文件.md) +* [07.GoFrame日志打印.md](doc_basic/07.goframe日志打印.md) +* [08.GoFrame数据库操作.md](doc_basic/08.goframe数据库操作.md) +* [09.GoFrameRedis操作.md](doc_basic/09.goframeRedis操作.md) +* [10.GoFrame常用工具介绍.md](doc_basic/10.goframe常用工具介绍.md) + +### 视频地址 + +* 腾讯课堂教程地址:[GoFrame基础教程-快速入门](https://ke.qq.com/course/2585407?taid=9440737551151935&tuin=13b4f9bd) +* bilibili教程地址:[GoFrame基础教程-快速入门](https://www.bilibili.com/video/bv157411Z7Le) +* 西瓜视频教程地址:[GoFrame基础教程-快速入门](https://www.ixigua.com/pseries/6809290943603147278/) + +## 二、GoFrame实战教程-登录篇 + +### 内容介绍 + +通过GoFrame实现登录流程;主要讲解模板引擎,登录实现,流程图工具,cookie和session介绍,数据校验,登录安全讲解等 + +### 目录结构 + +* [11.GoFrame登录实战之模板引擎.md](doc_login/11.GoFrame登录实战之模板引擎.md) +* [12.GoFrame登录实战之登录流程.md](doc_login/12.GoFrame登录实战之登录流程.md) +* [13.GoFrame登录实战之cookie和session](doc_login/13.GoFrame登录实战之cookie和session.md) +* [14.GoFrame登录实战之session实现](doc_login/14.GoFrame登录实战之session实现.md) +* [15.GoFrame登录实战之数据校验](doc_login/15.GoFrame登录实战之数据校验.md) +* [16.GoFrame登录实战之登录安全](doc_login/16.GoFrame登录实战之登录安全.md) + +### 视频地址 + +* 腾讯课堂教程地址:[GoFrame登录实战](https://ke.qq.com/course/2587868?taid=9171133864049884&tuin=13b4f9bd) +* bilibili教程地址:[GoFrame登录实战](https://www.bilibili.com/video/BV1oT4y1G7ge/) +* 西瓜视频教程地址:[GoFrame登录实战](https://www.ixigua.com/pseries/6817125437332783629/) + + +## 三、GoFrame工具链 + +### 内容介绍 + +GF工具链介绍:主要讲解安装,更新,项目初始化,热编译,交叉编译,model生成,打二进制包,docker等 + +### 目录结构 + +* [17.GoFrame工具链之基本介绍](doc_gf_tool_chain/17.GoFrame工具链之基本介绍.md) +* [18.GoFrame工具链之项目构建](doc_gf_tool_chain/18.GoFrame工具链之项目构建.md) +* [19.GoFrame工具链之代码生成](doc_gf_tool_chain/19.GoFrame工具链之代码生成.md) +* [20.GoFrame工具链之其他命令](doc_gf_tool_chain/20.GoFrame工具链之其他命令.md) + +### 视频地址 + +* 腾讯课堂教程地址:[GoFrame工具链](https://ke.qq.com/course/2628489?taid=9160688503626633&tuin=13b4f9bd) +* bilibili教程地址:[GoFrame工具链](https://www.bilibili.com/video/BV1YK4y1b7W8/) +* 西瓜视频教程地址:[GoFrame工具链](https://www.ixigua.com/pseries/6820830321651483148/) + +## GoFrame实战-正则表达式 + +### 内容介绍 + +主要通过示例、工具对正则表达式基本语法进行讲解,并通过gregex进行代码演示。 + +### 目录结构 + +* [21.gregex简介.md](doc_regex/21.gregex简介.md) +* [22.gregex正则详解.md](doc_regex/22.gregex正则详解.md) +* [23.gregex使用.md](doc_regex/23.gregex使用.md) + +### 视频地址 + +* 腾讯课堂教程地址:[GoFrame正则表达式实战](https://ke.qq.com/course/2993998?taid=10026334867533646&tuin=13b4f9bd) +* bilibili教程地址:[GoFrame正则表达式实战](https://www.bilibili.com/video/BV1Ct4y1S7zk/) +* 西瓜视频地址:[21.gregex简介](https://www.ixigua.com/6869794999031038471?logTag=czPBvnu07h4aDPFJHvFT8) +* 西瓜视频地址:[22.gregex正则详解一](https://www.ixigua.com/6869795168325730824?logTag=MYestcsXky2DXiLxcccBx) +* 西瓜视频地址:[23.gregex正则详解二.md](https://www.ixigua.com/6869795292573598216?logTag=ZIm5nLoomZ7_VYeCALhWb) +* 西瓜视频地址:[24.gregex使用.md](https://www.ixigua.com/6869795402028155399?logTag=AW8xrn6kna1XbZl5w_BeP) + +## 四、代码地址 + +* github:[https://github.com/goflyfox/gfstudy](https://github.com/goflyfox/gfstudy) +* gitee:[https://gitee.com/goflyfox/gfstudy](https://gitee.com/goflyfox/gfstudy) + + +## 五、其他说明 + +1. QQ交流群:47919644 +2. 对应教程编号前缀目录就是对应课程示例源码 + diff --git "a/doc_basic/01.goframe\344\273\213\347\273\215.assets/arch.png" "b/doc_basic/01.goframe\344\273\213\347\273\215.assets/arch.png" new file mode 100644 index 0000000..444ac8f Binary files /dev/null and "b/doc_basic/01.goframe\344\273\213\347\273\215.assets/arch.png" differ diff --git "a/doc_basic/01.goframe\344\273\213\347\273\215.md" "b/doc_basic/01.goframe\344\273\213\347\273\215.md" new file mode 100644 index 0000000..0e920d9 --- /dev/null +++ "b/doc_basic/01.goframe\344\273\213\347\273\215.md" @@ -0,0 +1,160 @@ +# GoFrame基础介绍 + +## 一、GO语言介绍 + +Go(又称 *Golang*)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 开发的一种跨平台(Mac OS、Windows、Linux 等)静态强类型、编译型语言。由Ken Thompson(肯·汤普森)联合创立,Unix操作系统的发明人之一(排在第一号)。 + +- docker,golang头号优秀项目,通过虚拟化技术实现的操作系统与应用的隔离,也称为容器; + +- kubernetes,是来自 Google 云平台的开源容器集群管理系统。简称k8s,k8s和docker是当前容器化技术的重要基础设施; + + + +golang基础教程-快速入门go语言 + +github:https://github.com/goflyfox/gostudy + +gitee:https://gitee.com/flyfox/gostudy + +## 二、GF基本介绍 + +GF(Go Frame)是一款模块化、高性能、生产级的Go基础开发框架。实现了比较完善的基础设施建设以及开发工具链,提供了常用的基础开发模块,如:缓存、日志、队列、数组、集合、容器、定时器、命令行、内存锁、对象池、配置管理、资源管理、数据校验、数据编码、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信等等。并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、Middleware、服务注册、模板引擎等等,支持热重启、热更新、域名绑定、TLS/HTTPS、Rewrite等特性。 + +## 三、GF特点 + +- 模块化、松耦合设计; +- **模块丰富,开箱即用;** +- 简便易用,易于维护; +- 社区活跃,大牛谦逊低调脾气好; +- 高代码质量、高单元测试覆盖率; +- 详尽的开发文档及示例; +- 完善的本地中文化支持; +- 更适合企业及团队使用; + +## 四、GF地址 + +- **主库**:https://github.com/gogf/gf +- **码云**:https://gitee.com/johng/gf +- **GF官网:**https://goframe.org/index + +目录结构及基本介绍: + +```bash +GF +├── container -- 基础类型:数组,通道,列表,map,队列,环,set,树,类型处理和转换 +│   ├── garray +│   ├── gchan +│   ├── glist +│   ├── gmap +│   ├── gpool +│   ├── gqueue +│   ├── gring +│   ├── gset +│   ├── gtree +│   ├── gtype +│   └── gvar +├── crypto -- 加密和解密:常用的md5,aes,3des +│   ├── gaes +│   ├── gcrc32 +│   ├── gdes +│   ├── gmd5 +│   └── gsha1 +├── database -- 数据库:关系型数据库(mysql,postgre,oracle)和redis +│   ├── gdb +│   └── gredis +├── debug -- 调试 +│   └── gdebug +├── DONATOR.MD +├── encoding --编解码:常用的base64和json +│   ├── gbase64 +│   ├── gbinary +│   ├── gcharset +│   ├── gcompress +│   ├── ghash +│   ├── ghtml +│   ├── gini +│   ├── gjson +│   ├── gparser +│   ├── gtoml +│   ├── gurl +│   ├── gxml +│   └── gyaml +├── errors -- 错误处理 +│   └── gerror +├── frame -- 核心框架:web,mvc +│   ├── g +│   ├── gins +│   └── gmvc +├── go.mod +├── i18n -- 国际化 +│   └── gi18n +├── internal 系统:空处理,锁,结构体 +│   ├── cmdenv +│   ├── empty +│   ├── fileinfo +│   ├── intlog +│   ├── mutex +│   ├── rwmutex +│   ├── structs +│   └── utils +├── LICENSE +├── net -- 网络:http,tpc,udp +│   ├── ghttp +│   ├── gipv4 +│   ├── gipv6 +│   ├── gsmtp +│   ├── gtcp +│   └── gudp +├── os -- 系统:定时任务,命令行交互,日志,文件处理,缓存,session,时间 +│   ├── gbuild +│   ├── gcache +│   ├── gcfg +│   ├── gcmd +│   ├── gcron +│   ├── genv +│   ├── gfcache +│   ├── gfile +│   ├── gfpool +│   ├── gfsnotify +│   ├── glog +│   ├── gmlock +│   ├── gmutex +│   ├── gproc +│   ├── gres +│   ├── grpool +│   ├── gsession +│   ├── gspath +│   ├── gtime +│   ├── gtimer +│   └── gview +├── README.MD +├── README_ZH.MD +├── RELEASE.1.MD +├── RELEASE.2.MD +├── test -- 单元测试 +│   └── gtest +├── text -- 文本处理:正则,字符串处理 +│   ├── gregex +│   └── gstr +├── TODO.MD +├── util -- 常用工具:类型转换,随机数,uuid,校验 +│   ├── gconv +│   ├── gmode +│   ├── gpage +│   ├── grand +│   ├── gutil +│   ├── guuid +│   └── gvalid +└── version.go +``` + + + +## 五、GF架构 + +![img](01.goframe介绍.assets/arch.png) + +## 六、GF交流 + +- QQ交流群:[116707870](https://shang.qq.com/wpa/qunwpa?idkey=195f91eceeb5d7fa76009b7cd5a4641f70bf4897b7f5a520635eb26ff17adfe7) +- WX交流群:微信添加`389961817`备注`GF`加群 \ No newline at end of file diff --git "a/doc_basic/02.goframe\345\237\272\347\241\200\347\216\257\345\242\203\346\220\255\345\273\272.assets/image-20200308224453465.png" "b/doc_basic/02.goframe\345\237\272\347\241\200\347\216\257\345\242\203\346\220\255\345\273\272.assets/image-20200308224453465.png" new file mode 100644 index 0000000..e4b062d Binary files /dev/null and "b/doc_basic/02.goframe\345\237\272\347\241\200\347\216\257\345\242\203\346\220\255\345\273\272.assets/image-20200308224453465.png" differ diff --git "a/doc_basic/02.goframe\345\237\272\347\241\200\347\216\257\345\242\203\346\220\255\345\273\272.md" "b/doc_basic/02.goframe\345\237\272\347\241\200\347\216\257\345\242\203\346\220\255\345\273\272.md" new file mode 100644 index 0000000..0c090dd --- /dev/null +++ "b/doc_basic/02.goframe\345\237\272\347\241\200\347\216\257\345\242\203\346\220\255\345\273\272.md" @@ -0,0 +1,136 @@ +# GoFrame基础环境搭建 + +## 一、环境搭建 + +之前基础教程有golang环境安装详细介绍,这里我只是快速过一下; + +### 1) 安装golang + +这里仅以windows为例: + +1. 去中文社区下载安装golang:https://studygolang.com/dl; +2. 下载go.{version}.windows-amd64.msi或者go.{version}.windows-amd64.zip包,此次使用go.{version}.windows-amd64.zip包 +3. 解压压缩文件(这里使用的是D:\Project,后面都基于这个目录) +4. 配置环境变量GOPATH和GOROOT + +```bash +# 打开cmd设置 +set GOPATH=D:\Project\GOPATH +set GOROOT=D:\Project\GO +set PATH=%PATH%;%GOROOT%\bin +``` + +当然应该将这些环境变量配置到系统环境变量中 + +4. 此时打开cmd窗口,运行`go version`即可展示安装golang版本 + +```go +# go version +go version go1.14 windows/amd64 +``` + +### 2)安装goland + +1. 官网下载goland:https://www.jetbrains.com/go/ +2. 安装注册购买或者破解; +3. 首先打开File->Setting或者Ctrl+Alt+S,设置goroot和gopath,默认会获取环境变量配置 +4. 需要开启go modules功能,然后配置代理;不配置代理会访问国外地址,会很慢;建议使用以下三个地址: + +- `https://goproxy.io` +- `https://goproxy.cn` +- `https://mirrors.aliyun.com/goproxy/` + +![image-20200308224453465](02.goframe基础环境搭建.assets/image-20200308224453465.png) + +### 3) 了解go modules + +go.mod`是Go项目的依赖描述文件: + +```go +module hello + +go 1.14 + +require github.com/gogf/gf v1.11.7 +``` + +1. module是配置项目名称 + +2. go配置的是使用的golang版本 + +3. require配置引用第三方依赖包路径和版本,latest表示最新版本; + +配置完编译成功后,生成`go.sum`依赖分析结果,里面会有当前所有的依赖详细信息; + +## 二、GF运行普通项目 + +通过go.mod引用goframe,构建下载,打印版本号;项目文件如下: + +### go.mod + +```go +module hello + +go 1.14 + +require github.com/gogf/gf v1.11.7 +``` + +### hello.go + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf" +) + +func main() { + fmt.Println("hello world!") + // 打印GF版本 + fmt.Println(gf.VERSION) +} +``` + +## 三、GF搭建web项目 + +让我们来运行第一个web程序 + +### go.mod + +```go +module hello + +go 1.14 + +require github.com/gogf/gf v1.11.7 +``` + +### main.go + +```go +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request){ + r.Response.Writeln("Welcome GoFrame!") + }) + s.BindHandler("/hello", func(r *ghttp.Request){ + r.Response.Writeln("Hello World!") + }) + + s.SetPort(80) + s.Run() +} +``` + +运行;然后打开浏览器,访问:http://127.0.0.1/和http://127.0.0.1/hello查看效果; + +web应用开发就是如此简单~!~ \ No newline at end of file diff --git "a/doc_basic/03.goframe\347\232\204WEB\346\234\215\345\212\241\344\273\213\347\273\215.assets/image-20200319225300542.png" "b/doc_basic/03.goframe\347\232\204WEB\346\234\215\345\212\241\344\273\213\347\273\215.assets/image-20200319225300542.png" new file mode 100644 index 0000000..14032ea Binary files /dev/null and "b/doc_basic/03.goframe\347\232\204WEB\346\234\215\345\212\241\344\273\213\347\273\215.assets/image-20200319225300542.png" differ diff --git "a/doc_basic/03.goframe\347\232\204WEB\346\234\215\345\212\241\344\273\213\347\273\215.md" "b/doc_basic/03.goframe\347\232\204WEB\346\234\215\345\212\241\344\273\213\347\273\215.md" new file mode 100644 index 0000000..8e6ce3f --- /dev/null +++ "b/doc_basic/03.goframe\347\232\204WEB\346\234\215\345\212\241\344\273\213\347\273\215.md" @@ -0,0 +1,95 @@ +# GoFrame的Web服务介绍 + +`GF`框架提供了非常强大的`WebServer`,由`ghttp`模块实现。实现了丰富完善的相关组件,例如:Router、Cookie、Session、路由注册、配置管理、模板引擎、缓存控制等等,支持热重启、热更新、多域名、多端口、多实例、HTTPS、Rewrite等等特性。 + +## 一、web基本介绍 + +我们的电脑浏览器(Browser)就是**客户端**(Client),大型的服务器就是**服务端**(Server);浏览器发送HTTP请求,即客户端通过网络将需求发给服务端,然后服务端也是通过网络将数据发给客户端; + +image-20200319225300542 + +## 二、GF搭建web项目 + +这里主要介绍基本项目启动和配置参数 + +### 目录结构 + +```bash +web:. +│ go.mod -- go module +│ go.sum +│ main.go -- 启动文件 +│ +├─config +│ config.toml --配置文件 +│ +├─gflogs +│ 2020-03-19.log -- gf系统日志 +│ access-20200319.log -- 访问日志 +│ error-20200319.log -- 异常日志 +│ +├─logs +│ 2020-03-19.log -- 业务日志 +│ +└─public + hello.html -- 静态文件 + index.html -- 静态入口文件 +``` + +### main.go + +```go +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/os/glog" +) + +func main() { + s := g.Server() + // 测试日志 + s.BindHandler("/welcome", func(r *ghttp.Request) { + glog.Info("你来了!") + glog.Error("你异常啦!") + r.Response.Write("哈喽世界!") + }) + // 异常处理 + s.BindHandler("/panic", func(r *ghttp.Request) { + glog.Panic("123") + }) + // post请求 + s.BindHandler("POST:/hello", func(r *ghttp.Request) { + r.Response.Writeln("Hello World!") + }) + s.Run() +} +``` + +### config.toml + +`GF`框架的核心组件均实现了便捷的文件配置管理方式,包括`Server`、日志组件、数据库ORM、模板引擎等等,非常强大便捷。 + +```ini +[server] + # 端口号 + Address = ":8199" + # 静态目录 + ServerRoot = "public" + # 入口文件 + IndexFiles = ["index.html", "main.html"] + # 系统访问日志 + AccessLogEnabled = true + # 系统异常日志panic + ErrorLogEnabled = true + # 系统日志目录,启动,访问,异常 + LogPath = "gflogs" + +[logger] + # 标准日志目录 + path = "logs" + # 日志级别 + level = "all" +``` + diff --git "a/doc_basic/04.goframe\350\267\257\347\224\261\346\263\250\345\206\214.assets/middleware.png" "b/doc_basic/04.goframe\350\267\257\347\224\261\346\263\250\345\206\214.assets/middleware.png" new file mode 100644 index 0000000..f1cbae9 Binary files /dev/null and "b/doc_basic/04.goframe\350\267\257\347\224\261\346\263\250\345\206\214.assets/middleware.png" differ diff --git "a/doc_basic/04.goframe\350\267\257\347\224\261\346\263\250\345\206\214.md" "b/doc_basic/04.goframe\350\267\257\347\224\261\346\263\250\345\206\214.md" new file mode 100644 index 0000000..fbb99b4 --- /dev/null +++ "b/doc_basic/04.goframe\350\267\257\347\224\261\346\263\250\345\206\214.md" @@ -0,0 +1,261 @@ +# GoFrame路由注册 + +## 一、路由规则 + +`gf`框架自建了非常强大的路由功能,提供了比任何同类框架更加出色的路由特性,支持流行的命名匹配规则、模糊匹配规则及字段匹配规则,并提供了优秀的优先级管理机制。 + +该方法是路由注册的最基础方法,其中的`pattern`为路由注册规则字符串,在其他路由注册方法中也会使用到,参数格式如下: + +```undefined +[HTTPMethod:]路由规则[@域名] +``` + +其中`HTTPMethod`(支持的Method:`GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE`)和`@域名`为非必需参数,一般来说直接给定路由规则参数即可,`BindHandler`会自动绑定**所有的**请求方式,如果给定`HTTPMethod`,那么路由规则仅会在该请求方式下有效。`@域名`可以指定生效的域名名称,那么该路由规则仅会在该域名下生效。 + +> `BindHandler`是最原生的路由注册方法,在大部分场景中,我们通常使用 **分组路由** 方式来管理路由 + +示例: + +```go + // hello方法,post调用 + s.BindHandler("POST:/hello", func(r *ghttp.Request) { + r.Response.Writeln("url" + r.Router.Uri) + }) +``` + +## 二、回调函数注册 + +回调函数注册方式是最简单且最灵活的的路由注册方式,注册的服务可以是一个实例化对象的方法地址,也可以是一个包方法地址。服务需要的数据可以通过`模块内部变量形式`或者`对象内部变量形式`进行管理,开发者可根据实际情况进行灵活控制。 + +我们可以直接通过`BindHandler`方法完成回调函数的注册,在框架的开发手册中很多地方都使用了回调函数注册的方式来做演示,因为这种注册方式比较简单。 + +示例: + +``` + // 方法注册 + s.BindHandler("/total", Total) +``` + +## 三、执行对象注册 + +执行对象注册是在注册时便给定一个实例化的对象,以后每一个请求都交给该对象(同一对象)处理,**该对象常驻内存不释放**。服务端进程在启动时便需要初始化这些执行对象,并且这些对象需要自行负责对自身数据的并发安全维护(往往对象的成员变量应当是并发安全的,每个请求执行完毕后该对象不会销毁,其成员变量也不会释放)。 + +```go + // 对象注册 + c := new(Controller) + s.BindObject("POST:/object", c) +``` + +## 四、分组注册 + +`GF`框架支持分组路由的注册方式,可以给分组路由指定一个`prefix`前缀(也可以直接给定`/`前缀,表示注册在根路由下),在该分组下的所有路由注册都将注册在该路由前缀下。分组路由注册方式也是推荐的路由注册方式。 + +示例: + +``` + // 分组注册及中间件 + group := s.Group("/api") + group.ALL("/all", func(r *ghttp.Request) { + r.Response.Writeln("all") + }) +``` + +## 五、中间件设计 + +`GF`提供了优雅的中间件请求控制方式,该方式也是主流的`WebServer`提供的请求流程控制方式,基于中间件设计可以为`WebServer`提供更灵活强大的插件机制。经典的中间件洋葱模型: + +![经典的中间件洋葱模型](04.goframe路由注册.assets/middleware.png) + +示例: + +```go + // 分组注册及中间件 + group := s.Group("/api") + group.Middleware(MiddlewareTest) + group.ALL("/all", func(r *ghttp.Request) { + r.Response.Writeln("all") + }) +``` + +## 六、请求和响应对象 + +### 请求Request + +请求输入依靠 `ghttp.Request` 对象实现,`ghttp.Request`继承了底层的`http.Request`对象。`ghttp.Request`包含一个与当前请求对应的返回输出对象`Response`,用于数据的返回处理。 + +可以看到`Request`对象的参数获取方法非常丰富,可以分为以下几类: + +1. `Get*`: 常用方法,简化参数获取,`GetRequest*`的别名。 +2. `GetQuery*`: 获取`GET`方式传递过来的参数,包括`Query String`及`Body`参数解析。 +3. `GetForm*`: 获取表单方式传递过来的参数,表单方式提交的参数`Content-Type`往往为`application/x-www-form-urlencoded`, `application/form-data`, `multipart/form-data`, `multipart/mixed`等等。 +4. `GetRequest*`: 获取客户端提交的参数,不区分提交方式。 +5. `Get*Struct`: 将指定类型的请求参数绑定到指定的`struct`对象上,注意给定的参数为对象指针。绝大部分场景中往往使用`Parse`方法将请求数据转换为请求对象,具体详见后续章节。 +6. `GetBody/GetBodyString`: 获取客户端提交的原始数据,该数据是客户端写入到`body`中的原始数据,与`HTTP Method`无关,例如客户端提交`JSON/XML`数据格式时可以通过该方法获取原始的提交数据。 +7. `GetJson`: 自动将原始请求信息解析为`gjson.Json`对象指针返回。 +8. `Exit*`: 用于请求流程退出控制; + +### 响应Response + +`ghttp.Response`对象实现了标准库的`http.ResponseWriter`接口。数据输出使用`Write*`相关方法实现,并且数据输出采用了`Buffer`机制,因此数据的处理效率比较高。任何时候可以通过`OutputBuffer`方法输出缓冲区数据到客户端,并清空缓冲区数据。 + +简要说明: + +1. `Write*`方法用于数据的输出,可为任意的数据格式,内部通过断言对参数做自动分析。 +2. `Write*Exit`方法用于数据输出后退出当前服务方法,可用于替代`return`返回方法。 +3. `WriteJson*`/`WriteXml`方法用于特定数据格式的输出,这是为开发者提供的简便方法。 +4. `WriteTpl*`方法用于模板输出,解析并输出模板文件,也可以直接解析并输出给定的模板内容。 +5. `ParseTpl*`方法用于模板解析,解析模板文件或者模板内容,返回解析后的内容。 + +## 七、教程示例 + +```go +package main + +import ( + "github.com/gogf/gf/container/gtype" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +func main() { + s := g.Server() + // 常规注册 + // hello方法,post调用 + s.BindHandler("POST:/hello", func(r *ghttp.Request) { + r.Response.Writeln("url" + r.Router.Uri) + }) + // 所有方法,url包含name参数 + s.BindHandler("/:name", func(r *ghttp.Request) { + // 获取URL name参数 + r.Response.Writeln("name:" + r.GetString("name")) + r.Response.Writeln("url" + r.Router.Uri) + }) + // 所有方法,url包含name参数 + s.BindHandler("/:name/update", func(r *ghttp.Request) { + r.Response.Writeln("name:" + r.GetString("name")) + r.Response.Writeln("url" + r.Router.Uri) + }) + // 所有方法,url包含name和action参数 + s.BindHandler("/:name/:action", func(r *ghttp.Request) { + r.Response.Writeln("name:" + r.GetString("name")) + r.Response.Writeln("action:" + r.GetString("action")) + r.Response.Writeln("url" + r.Router.Uri) + }) + // 所有方法,url包含field属性 + s.BindHandler("/user/list/{field}.html", func(r *ghttp.Request) { + // 获取URL field属性 + r.Response.Writeln("field:" + r.GetString("field")) + r.Response.Writeln("url" + r.Router.Uri) + }) + + // 方法注册 + s.BindHandler("/total", Total) + + // 对象注册 + c := new(Controller) + s.BindObject("POST:/object", c) + + + // 分组注册及中间件 + group := s.Group("/api") + group.Middleware(MiddlewareTest) + group.ALL("/all", func(r *ghttp.Request) { + r.Response.Writeln("all") + }) + group.GET("/get", func(r *ghttp.Request) { + r.Response.Writeln("get") + }) + group.POST("/post", func(r *ghttp.Request) { + r.Response.Writeln("post") + }) + + // request and response + s.BindHandler("POST:/test", func(r *ghttp.Request) { + r.Response.WriteJson(g.Map{ + "name":r.GetString("name"), + "age":r.GetInt("age"), + "sex":r.Header.Get("sex"), + }) + }) + + s.SetPort(8199) + s.Run() +} + +var ( + total = gtype.NewInt() +) + +func Total(r *ghttp.Request) { + r.Response.Write("total:", total.Add(1)) +} + +// 对象注册 +type Controller struct{} + +func (c *Controller) Index(r *ghttp.Request) { + r.Response.Write("index") +} + +func (c *Controller) Show(r *ghttp.Request) { + r.Response.Write("show") +} + +// 中间件 +func MiddlewareTest(r *ghttp.Request) { + // 前置逻辑 + r.Response.Writeln("###start") + r.Middleware.Next() + // 后置逻辑 + r.Response.Writeln("###end") +} +``` + +访问结果: + +```go +### 常规注册 +POST http://localhost:8199/hello + +### +GET http://localhost:8199/abc + +### +GET http://localhost:8199/a/add + +### +GET http://localhost:8199/a/update + +### +GET http://localhost:8199/user/list/11.html + +### 方法注册 +GET http://localhost:8199/total + +### 对象注册,默认访问index +POST http://localhost:8199/object/ + +### 对象注册,直接访问Index +POST http://localhost:8199/object/index + +### 对象注册,访问show方法 +POST http://localhost:8199/object/show + +### 分组,默认访问index +PUT http://localhost:8199/api/all + +### 对象注册,直接访问Index +GET http://localhost:8199/api/get + +### 对象注册,访问show方法 +POST http://localhost:8199/api/post + +### request and response +POST http://localhost:8199/test +sex:man + +name=liubang&age=18 + +### +``` + diff --git "a/doc_basic/05.goframe\347\232\204HTTP\345\256\242\346\210\267\347\253\257.assets/image-20200316235457715.png" "b/doc_basic/05.goframe\347\232\204HTTP\345\256\242\346\210\267\347\253\257.assets/image-20200316235457715.png" new file mode 100644 index 0000000..9752020 Binary files /dev/null and "b/doc_basic/05.goframe\347\232\204HTTP\345\256\242\346\210\267\347\253\257.assets/image-20200316235457715.png" differ diff --git "a/doc_basic/05.goframe\347\232\204HTTP\345\256\242\346\210\267\347\253\257.assets/image-20200316235850132.png" "b/doc_basic/05.goframe\347\232\204HTTP\345\256\242\346\210\267\347\253\257.assets/image-20200316235850132.png" new file mode 100644 index 0000000..09f7e1b Binary files /dev/null and "b/doc_basic/05.goframe\347\232\204HTTP\345\256\242\346\210\267\347\253\257.assets/image-20200316235850132.png" differ diff --git "a/doc_basic/05.goframe\347\232\204HTTP\345\256\242\346\210\267\347\253\257.assets/image-20200316235911263.png" "b/doc_basic/05.goframe\347\232\204HTTP\345\256\242\346\210\267\347\253\257.assets/image-20200316235911263.png" new file mode 100644 index 0000000..fbd2294 Binary files /dev/null and "b/doc_basic/05.goframe\347\232\204HTTP\345\256\242\346\210\267\347\253\257.assets/image-20200316235911263.png" differ diff --git "a/doc_basic/05.goframe\347\232\204HTTP\345\256\242\346\210\267\347\253\257.md" "b/doc_basic/05.goframe\347\232\204HTTP\345\256\242\346\210\267\347\253\257.md" new file mode 100644 index 0000000..593d8ca --- /dev/null +++ "b/doc_basic/05.goframe\347\232\204HTTP\345\256\242\346\210\267\347\253\257.md" @@ -0,0 +1,179 @@ +# GoFrame的HTTP客户端 + +## 一、HTTP协议介绍 + +超文本传输协议(英文:**H**yper**T**ext **T**ransfer **P**rotocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。 + +请求: + +![image-20200316235850132](05.goframe的HTTP客户端.assets/image-20200316235850132.png) + +响应: + +![image-20200316235911263](05.goframe的HTTP客户端.assets/image-20200316235911263.png) + +放问GF启动的网址,通过Chrome F12查看NetWork中的URL; + +![image-20200316235457715](05.goframe的HTTP客户端.assets/image-20200316235457715.png) + +**优点:简单方便,浏览器支持完善,工具链成熟;** + +## 二、GF的HTTP客户端 + +这个先启动一个gf的http server,然后我们通过go test 来测试ghttp client; + +### main.go + +```go +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +func main() { + s := g.Server() + group := s.Group("/api") + // 默认路径 + group.ALL("/", func(r *ghttp.Request) { + r.Response.Writeln("Welcome GoFrame!") + }) + // GET带参数 + group.GET("/hello", func(r *ghttp.Request) { + r.Response.Writeln("Hello World!") + r.Response.Writeln("name:", r.GetString("name")) + }) + // POST KV + group.POST("/test", func(r *ghttp.Request) { + r.Response.Writeln("func:test") + r.Response.Writeln("name:", r.GetString("name")) + r.Response.Writeln("age:", r.GetInt("age")) + }) + // POST JSON + group.POST("/test2", func(r *ghttp.Request) { + r.Response.Writeln("func:test2") + r.Response.Writeln("passport:", r.GetString("passport")) + r.Response.Writeln("password:", r.GetString("password")) + }) + // POST Header + group.POST("/test3", func(r *ghttp.Request) { + r.Response.Writeln("func:test3") + r.Response.Writeln("Cookie:", r.Header.Get("Cookie")) + }) + // POST Header + group.POST("/test4", func(r *ghttp.Request) { + r.Response.Writeln("func:test4") + h := r.Header + r.Response.Writeln("accept-encoding:", h.Get("accept-encoding")) + r.Response.Writeln("accept-language:", h.Get("accept-language")) + r.Response.Writeln("referer:", h.Get("referer")) + r.Response.Writeln("cookie:", h.Get("cookie")) + r.Response.Writeln(r.Cookie.Map()) + }) + + s.SetPort(80) + s.Run() +} +``` + +### client_test.go + +单元测试源码文件可以由多个测试用例组成,每个测试用例函数需要以`Test`为前缀,例如: + +func TestXXX( t *testing.T ) + +- 测试用例文件不会参与正常源码编译,不会被包含到可执行文件中。 +- 测试用例文件使用`go test`指令来执行,没有也不需要 main() 作为函数入口。所有在以`_test`结尾的源码内以`Test`开头的函数会自动被执行。 +- 测试用例可以不传入 *testing.T 参数。 + +```go +package test + +import ( + "fmt" + "github.com/gogf/gf/net/ghttp" + "testing" +) + +var path = "http://127.0.0.1/api" + +// GET请求 +func TestGet(t *testing.T) { + if response, err := ghttp.Get(path); err != nil { + panic(err) + } else { + defer response.Close() + t.Log(response.ReadAllString()) + } + if response, err := ghttp.Post(path); err != nil { + panic(err) + } else { + defer response.Close() + t.Log(response.ReadAllString()) + } +} + +// GET请求带参数 +func TestHello(t *testing.T) { + if response, err := ghttp.Get(path + "/hello?name=whoami"); err != nil { + panic(err) + } else { + defer response.Close() + t.Log(response.ReadAllString()) + } +} + +// POST请求 +func TestPost(t *testing.T) { + if response, err := ghttp.Post(path+"/test", "name=john&age=18"); err != nil { + panic(err) + } else { + defer response.Close() + t.Log(response.ReadAllString()) + } +} + +// POST JSON +func TestPostJson(t *testing.T) { + if response, err := ghttp.Post(path+"/test2", + `{"passport":"john","password":"123456"}`); err != nil { + panic(err) + } else { + defer response.Close() + t.Log(response.ReadAllString()) + } +} + +// POST Header头 +func TestPostHeader(t *testing.T) { + c := ghttp.NewClient() + c.SetHeader("Cookie", "name=john; score=100") + if r, e := c.Post(path + "/test3"); e != nil { + panic(e) + } else { + fmt.Println(r.ReadAllString()) + } +} + +// POST Header头 +func TestPostHeader2(t *testing.T) { + c := ghttp.NewClient() + c.SetHeaderRaw(` +accept-encoding: gzip, deflate, br +accept-language: zh-CN,zh;q=0.9,en;q=0.8 +referer: https://idonottell.you +cookie: name=john; score=100 +user-agent: my test http client + `) + if r, e := c.Post(path + "/test4"); e != nil { + panic(e) + } else { + fmt.Println(r.ReadAllString()) + } +} +``` + + + +**最后,建议初学者搜索下 HTTP协议进行学习;** \ No newline at end of file diff --git "a/doc_basic/06.goframe\351\205\215\347\275\256\346\226\207\344\273\266.md" "b/doc_basic/06.goframe\351\205\215\347\275\256\346\226\207\344\273\266.md" new file mode 100644 index 0000000..b362faa --- /dev/null +++ "b/doc_basic/06.goframe\351\205\215\347\275\256\346\226\207\344\273\266.md" @@ -0,0 +1,165 @@ +# GoFrame配置文件 + +## 一、配置文件介绍 + +`GF`的配置管理由`gcfg`模块实现,`gcfg`模块是并发安全的,仅提供配置文件读取功能,不提供数据写入/修改功能,**支持的数据文件格式包括: `JSON`、`XML`、`YAML/YML`、`TOML`、`INI`**,项目中开发者可以灵活地选择自己熟悉的配置文件格式来进行配置管理。 + +**默认读取执行文件所在目录及其下的`config`目录,默认读取的配置文件为`config.toml`**;`toml`类型文件也是默认的、推荐的配置文件格式,如果想要自定义文件格式,可以通过`SetFileName`方法修改默认读取的配置文件名称(如:`config.json`, `cfg.yaml`, `cfg.xml`, `cfg.ini`等等)。 + +注:TOML大小写敏感,必须是`UTF-8`编码; + +## 二、自动检测更新 + +配置管理器使用了缓存机制,当配置文件第一次被读取后会被缓存到内存中,下一次读取时将会直接从缓存中获取,以提高性能。同时,配置管理器提供了对配置文件的**自动检测更新机制**,当配置文件在外部被修改后,配置管理器能够即时地刷新配置文件的缓存内容。 + +配置管理器的自动检测更新机制是`gf`框架特有的一大特色。 + +## 三、示例 + +### 项目目录 + +```bash +D:. +│ config_test.go -- 测试文件 +│ go.mod +│ go.sum +│ main.go -- web自动更新配置演示 +│ +├─config +│ config.toml -- 标准配置文件 +│ +└─configTest -- 定制目录和配置文件 + config1.toml + config2.toml +``` + +### config.toml + +```toml +# 模板引擎目录 +viewpath = "/home/www/templates/" +# MySQL数据库配置 +[database] + [[database.default]] + host = "127.0.0.1" + port = "3306" + user = "root" + pass = "123456" + name = "test1" + type = "mysql" + role = "master" + charset = "utf8" + priority = "1" + [[database.default]] + host = "127.0.0.1" + port = "3306" + user = "root" + pass = "123456" + name = "test2" + type = "mysql" + role = "master" + charset = "utf8" + priority = "1" +# Redis数据库配置 +[redis] + disk = "127.0.0.1:6379,0" + cache = "127.0.0.1:6379,1" +``` + +### config1.toml + +```toml +study = "hello study" +study1 = "hello study1" +``` + +### config2.toml + +```toml +config2 = "111" +``` + +### main.go + +```go +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +func main() { + s := g.Server() + // 默认路径 + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.Writeln("配置",g.Config().GetString("name")) + r.Response.Writeln("Welcome GoFrame!") + }) + + s.SetPort(80) + s.Run() + +} + +``` + +### config_test.go + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/frame/g" + "testing" +) + +// 基本配置使用 +func TestConfig(t *testing.T) { + // 默认当前路径或者config路径,默认文件config.toml + // /home/www/templates/ + fmt.Println(g.Config().Get("viewpath")) + fmt.Println(g.Cfg().Get("viewpath")) + // 127.0.0.1:6379,1 + c := g.Cfg() + // 分组方式 + fmt.Println(c.Get("redis.cache")) + // 数组方式:test2 + fmt.Println(c.Get("database.default.1.name")) +} + +// 设置路径 +func TestConfig2(t *testing.T) { + // 设置加载文件,默认name为default + // 设置路径 + g.Cfg().SetPath("configTest") + // 设置加载文件 + g.Cfg().SetFileName("config1.toml") + + // 打印测试 + fmt.Println(g.Cfg().Get("viewpath")) + fmt.Println(g.Cfg().Get("study")) + fmt.Println(g.Cfg().Get("study1")) + fmt.Println(g.Cfg().Get("config2")) + + // 新的name就是新的实例 + g.Cfg("name").SetPath("configTest") + g.Cfg("name").SetFileName("config2.toml") + fmt.Println(g.Cfg("name").Get("viewpath")) + fmt.Println(g.Cfg("name").Get("study")) + fmt.Println(g.Cfg("name").Get("study1")) + fmt.Println(g.Cfg("name").Get("config2")) +} +``` + +### go.mod + +```go +module gf_config + +go 1.14 + +require github.com/gogf/gf v1.11.7 +``` + diff --git "a/doc_basic/07.goframe\346\227\245\345\277\227\346\211\223\345\215\260.md" "b/doc_basic/07.goframe\346\227\245\345\277\227\346\211\223\345\215\260.md" new file mode 100644 index 0000000..76be8fc --- /dev/null +++ "b/doc_basic/07.goframe\346\227\245\345\277\227\346\211\223\345\215\260.md" @@ -0,0 +1,141 @@ +# GoFrame日志打印 + +## 一、日志介绍 + +`glog`是通用的高性能日志管理模块,实现了强大易用的日志管理功能,是`gf`开发框架的核心模块之一。 + +重要的几点说明: + +1. `glog`采用了无锁设计,性能高效; +2. `glog`支持文件输出、日志级别、日志分类、调试管理、调用跟踪、链式操作等等丰富特性; +3. 可以使用`glog.New`方法创建`glog.Logger`对象用于自定义日志打印,也可以并推荐使用`glog`默认提供的包方法来打印日志; +4. 当使用包方法修改模块配置时,注意任何的`glog.Set*`设置方法都将会**全局生效**; +5. 日志内容默认时间格式为 `时间 [级别] 内容 换行`,其中`时间`精确到毫秒级别,`级别`为可选输出,`内容`为调用端的参数输入,`换行`为可选输出(部分方法自动为日志内容添加换行符号),日志内容示例:`2018-10-10 12:00:01.568 [ERRO] 产生错误`; +6. `Print*/Debug*/Info*`方法输出日志内容到标准输出(`stdout`),为防止日志的错乱,`Notice*/Warning*/Error*/Critical*/Panic*/Fatal*`方法也是将日志内容输出到标准输出(`stdout`); +7. 其中`Panic*`方法在输出日志信息后会引发`panic`错误方法,`Fatal*`方法在输出日志信息之后会停止进程运行,并返回进程状态码值为`1`(正常程序退出状态码为`0`); + +## 二、单例对象 + +从`GF v1.10`版本开始,日志组件支持单例模式,使用`g.Log(单例名称)`获取不同的单例日志管理对象。提供单例对象的目的在于针对不同业务场景可以使用不同配置的日志管理对象。 + +## 三、日志级别 + +日志级别用于管理日志的输出,我们可以通过设定特定的日志级别来关闭/开启特定的日志内容。通过`SetLevel`方法可以设置日志级别,`glog`支持以下几种日志级别常量设定: + +```go +LEVEL_ALL +LEVEL_DEBU +LEVEL_INFO +LEVEL_NOTI +LEVEL_WARN +LEVEL_ERRO +LEVEL_CRIT +``` + +我们可以通过`位操作`组合使用这几种级别,例如其中`LEVEL_ALL`等价于`LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT`。例如我们可以通过`LEVEL_ALL & ^LEVEL_DEBU & ^LEVEL_INFO & ^LEVEL_NOTI`来过滤掉`LEVEL_DEBU/LEVEL_INFO/LEVEL_NOTI`日志内容。 + +## 四、配置文件 + +日志组件支持配置文件,当使用`g.Log(单例名称)`获取`Logger`单例对象时,将会自动通过默认的配置管理对象获取对应的`Logger`配置。默认情况下会读取`logger.单例名称`配置项,当该配置项不存在时,将会读取`logger`配置项。 + +```toml +[logger] + # 日志目录 + path = "logs" + # all LEVEL_ALL = LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT + # dev LEVEL_DEV = LEVEL_ALL + # pro LEVEL_PROD = LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT + level = "all" + # 是否打印到控制台 + stdout = true + [logger.logger1] + path = "logger1" + level = "dev" + stdout = true + [logger.logger2] + path = "logger2" + level = "prod" + stdout = false +``` + + + +## 五、示例 + +### 项目目录 + +```bash +D:. +│ go.mod +│ go.sum +│ main.go +│ +└─config + config.toml +``` + +### main.go + +```go +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/os/glog" +) + +func main() { + // 对应默认配置项 logger,默认default + g.Log().Debug("[default]Debug") + g.Log().Info("[default]info") + g.Log().Warning("[default]Warning") + g.Log().Error("[default]Error") + // 对应 logger.logger1 配置项 + g.Log("logger1").Debug("[logger1]Debug") + g.Log("logger1").Info("[logger1]info") + g.Log("logger1").Warning("[logger1]Warning") + g.Log("logger1").Error("[logger1]Error") + // 对应 logger.logger2 配置项 + g.Log("logger2").Debug("[logger2]Debug") + g.Log("logger2").Info("[logger2]info") + g.Log("logger2").Warning("[logger2]Warning") + g.Log("logger2").Error("[logger2]Error") + + + // 日志级别设置,过滤掉Info日志信息 + l := glog.New() + l.Info("info1") + l.SetLevel(glog.LEVEL_ALL ^ glog.LEVEL_INFO) + l.Info("info2") + // 支持哪些级别 + // LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT + + // 异常 + g.Log().Panic("this is panic!") + g.Log().Info("............") + +} +``` + +### config.toml + +```toml +[logger] + # 日志目录 + path = "logs" + # all LEVEL_ALL = LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT + # dev LEVEL_DEV = LEVEL_ALL + # pro LEVEL_PROD = LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT + level = "all" + # 是否打印到控制台 + stdout = true + [logger.logger1] + path = "logger1" + level = "dev" + stdout = true + [logger.logger2] + path = "logger2" + level = "prod" + stdout = false +``` + diff --git "a/doc_basic/08.goframe\346\225\260\346\215\256\345\272\223\346\223\215\344\275\234.md" "b/doc_basic/08.goframe\346\225\260\346\215\256\345\272\223\346\223\215\344\275\234.md" new file mode 100644 index 0000000..7fc615d --- /dev/null +++ "b/doc_basic/08.goframe\346\225\260\346\215\256\345\272\223\346\223\215\344\275\234.md" @@ -0,0 +1,217 @@ +# Goframe数据库操作 + +## 一、基本介绍 + +`gf`框架的`ORM`功能由`gdb`模块实现,用于常用关系型数据库的`ORM`操作。其最大的特色在于同时支持`map`和`struct`两种方式操作数据库。`gdb`默认情况下使用的是`map`数据类型作为基础的数据表记录载体,开发者无需预先定义数据表记录`struct`便可直接对数据表记录执行各种操作。这样的设计赋予了开发者更高的灵活度和简便性。 + +支持的数据库类型:Mysql,SQLite,PostgreSQL,SQLServer,Oracle + +## 二、配置文件 + +> 推荐使用配置文件及单例对象来管理和使用数据库操作。 + +如果我们使用`g`对象管理模块中的`g.DB("数据库分组名称")`方法获取数据库操作对象,数据库对象将会自动读取`config.toml`配置文件中的相应配置项(通过配置管理模块),并自动初始化该数据库操作的单例对象。 + +```toml +[database] + [[database.default]] + link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + [[database.user]] + link = "mysql:root:12345678@tcp(127.0.0.1:3306)/user" +``` + +注意每一项分组配置均可以是多个节点,支持负载均衡权重策略。如果不使用多节点负载均衡特性,仅使用配置分组特性,也可以简化为如下格式: + +```toml +[database] + [database.default] + link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + [database.user] + link = "mysql:root:12345678@tcp(127.0.0.1:3306)/user" +``` + +如果仅仅是单数据库节点,不使用配置分组特性,那么也可以简化为如下格式: + +```toml +[database] + link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test" +``` + +不同数据类型对应的`linkinfo`如下: + +| 数据库类型 | Linkinfo配置 | 更多参数 | +| :--------- | :----------------------------------------------------------- | :----------------------------------------------------- | +| mysql | `mysql: 账号:密码@tcp(地址:端口)/数据库名称` | [mysql](https://github.com/go-sql-driver/mysql) | +| pgsql | `pgsql: user=账号 password=密码 host=地址 port=端口 dbname=数据库名称` | [pq](https://github.com/lib/pq) | +| mssql | `mssql: user id=账号;password=密码;server=地址;port=端口;database=数据库名称;encrypt=disable` | [go-mssqldb](https://github.com/denisenkom/go-mssqldb) | +| sqlite | `sqlite: 文件绝对路径` (如: `/var/lib/db.sqlite3`) | [go-sqlite3](https://github.com/mattn/go-sqlite3) | +| oracle | `oracle: 账号/密码@地址:端口/数据库名称` | [go-oci8](https://github.com/mattn/go-oci8) | + +## 三、日志输出配置 + +`gdb`支持日志输出,内部使用的是`glog.Logger`对象实现日志管理,并且可以通过配置文件对日志对象进行配置。默认情况下`gdb`关闭了`DEBUG`日志输出,如果需要打开`DEBUG`信息需要将数据库的`debug`参数设置为`true`。以下是为一个配置文件示例: + +```toml +[database] + [database.logger] + Path = "/var/log/gf-app/sql" + Level = "all" + Stdout = true + [database.primary] + link = "mysql:root:12345678@tcp(127.0.0.1:3306)/user_center" + debug = true +``` + +其中`database.logger`即为`gdb`的日志配置,当该配置不存在时,将会使用日志组件的默认配置 + +## 四、数据结构 + +为便于数据表记录的操作,ORM定义了5种基本的数据类型: + +```go +type Map map[string]interface{} // 数据记录 +type List []Map // 数据记录列表 + +type Value *gvar.Var // 返回数据表记录值 +type Record map[string]Value // 返回数据表记录键值对 +type Result []Record // 返回数据表记录列表 +``` + +1. `Map`与`List`用于ORM操作过程中的输入参数类型(与全局类型`g.Map`和`g.List`一致,在项目开发中常用`g.Map`和`g.List`替换); +2. `Value/Record/Result`用于ORM操作的结果数据类型; + +## 五、数据库操作 + +### `Insert/Replace/Save` + +这三个链式操作方法用于数据的写入,并且支持自动的单条或者批量的数据写入,三者区别如下: + +1. `Insert` + + 使用`INSERT INTO`语句进行数据库写入,如果写入的数据中存在主键或者唯一索引时,返回失败,否则写入一条新数据; + +2. `Replace` + + 使用`REPLACE INTO`语句进行数据库写入,如果写入的数据中存在主键或者唯一索引时,会删除原有的记录,必定会写入一条新记录; + +3. `Save` + + 使用`INSERT INTO`语句进行数据库写入,如果写入的数据中存在主键或者唯一索引时,更新原有数据,否则写入一条新数据; + +> 在部分数据库类型中,并不支持`Replace/Save`方法 + +### `Update`更新方法 + +`Update`用于数据的更新,往往需要结合`Data`及`Where`方法共同使用。`Data`方法用于指定需要更新的数据,`Where`方法用于指定更新的条件范围。同时,`Update`方法也支持直接给定数据和条件参数。 + +### `Delete`删除方法 + +`Delete`方法用于数据的删除。 + +### `Where/And/Or`查询条件 + +这三个方法用于传递查询条件参数,支持的参数为任意的`string/map/slice/struct/*struct`类型。 + +`Where`条件参数推荐使用字符串的参数传递方式(并使用`?`占位符预处理),因为`map`/`struct`类型作为查询参数无法保证顺序性,且在部分情况下(数据库有时会帮助你自动进行查询索引优化),数据库的索引和你传递的查询条件顺序有一定关系。 + +当使用多个`Where`方法连接查询条件时,作用同`And`。 此外,当存在多个查询条件时,`gdb`会默认将多个条件分别使用`()`符号进行包含,这种设计可以非常友好地支持查询条件分组。 + +### `All/One/Value/Count`数据查询 + +这四个方法是数据查询比较常用的方法: + +1. `All` 用于查询并返回多条记录的列表/数组。 +2. `One` 用于查询并返回单条记录。 +3. `Value` 用于查询并返回一个字段值,往往需要结合`Fields`方法使用。 +4. `Count` 用于查询并返回记录数。 + +此外,也可以看得到这四个方法定义中也支持条件参数的直接输入,参数类型与`Where`方法一致。但需要注意,其中`Value`方法的参数中至少应该输入字段参数。 + +### 数据库表 + +```sql +CREATE TABLE `user` ( + `uid` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT NULL, + `site` varchar(255) DEFAULT NULL, + PRIMARY KEY (`uid`) +) ENGINE=InnoDB AUTO_INCREMENT=10000 ; +``` + +### 示例 + +```go +package test + +import ( + "fmt" + "github.com/gogf/gf/frame/g" + "testing" +) + +// Insert +func TestInsert(t *testing.T) { + // INSERT INTO `user`(`name`) VALUES('john') + _, err := g.DB().Table("user").Data(g.Map{"uid": 10000, "name": "john"}).Insert() + if err != nil { + panic(err) + } +} + +// Update +func TestUpdate(t *testing.T) { + // UPDATE `user` SET `name`='john guo' WHERE name='john' + _, err := g.DB().Table("user").Data("name", "john guo"). + Where("name", "john").Update() + if err != nil { + panic(err) + } +} + +// Delete +func TestDelete(t *testing.T) { + // DELETE FROM `user` WHERE uid=10 + _, err := g.DB().Table("user").Where("uid", 10000).Delete() + if err != nil { + panic(err) + } +} + +// Select Where +func TestWhere(t *testing.T) { + // INSERT INTO `user`(`name`) VALUES('john') + g.DB().Table("user").Data(g.Map{"uid": 10001, "name": "john"}).Insert() + g.DB().Table("user").Data(g.Map{"uid": 10002, "name": "john2"}).Insert() + // 数量 + count, err := g.DB().Table("user").Where("uid", 10001).Count() + if err != nil { + panic(err) + } + fmt.Println("count:", count) + // 获取单个值 + v, err := g.DB().Table("user").Where("uid", 10001).Fields("name").Value() + if err != nil { + panic(err) + } + fmt.Println("name:", v.String()) + // 查询对象 + r, err := g.DB().Table("user").Where("uid", 10002).One() + if err != nil { + panic(err) + } + fmt.Println("name:", r.Map()["name"]) + // 查询对象 + //l, err := g.DB().Table("user").As("t").Where("t.uid > ?", 10000).All() + // 也可以简写为 select * from user as t where t.uid > 10000 + l, err := g.DB().Table("user").As("t").All("t.uid > ?", 10000) + if err != nil { + panic(err) + } + for index, value := range l { + fmt.Println(index, value["uid"], value["name"]) + } + g.DB().Table("user").Where("uid", 10001).Delete() + g.DB().Table("user").Where("uid", 10002).Delete() +} +``` + diff --git "a/doc_basic/09.goframeRedis\346\223\215\344\275\234.md" "b/doc_basic/09.goframeRedis\346\223\215\344\275\234.md" new file mode 100644 index 0000000..f423ff0 --- /dev/null +++ "b/doc_basic/09.goframeRedis\346\223\215\344\275\234.md" @@ -0,0 +1,131 @@ +# GoFrame Redis操作 + +Redis客户端由`gredis`模块实现,底层采用了链接池设计。 + +## 一、Redis介绍 + +Redis是当前比较热门的NOSQL系统之一,它是一个开源的使用ANSI c语言编写的**key-value**存储系统(区别于MySQL的二维表格的形式存储。)。性能出色:Redis读取的速度是110000次/s,写的速度是81000次/s。 + +### 支持类型 + +String: 字符串、Hash: 散列、List: 列表、Set: 集合、Sorted Set: 有序集合 + +PUB/SUB:发布订阅; + +在5.0支持了全新数据类型:Streams + +### 使用场景 + +缓存,登录验证码,消息队列,过滤器,分布式锁,限流等 + +## 二、Redis配置文件 + +绝大部分情况下推荐使用`g.Redis`单例方式来操作redis。因此同样推荐使用配置文件来管理Redis配置,在`config.toml`中的配置示例如下: + +```toml +# Redis数据库配置 +[redis] + default = "127.0.0.1:6379,0" + cache = "127.0.0.1:6379,1,123456?idleTimeout=600" +``` + +其中,Redis的配置格式为:`host:port[,db,pass?maxIdle=x&maxActive=x&idleTimeout=x&maxConnLifetime=x]` + +各配置项说明如下: + +| 配置项名称 | 是否必须 | 默认值 | 说明 | +| :-------------- | :------- | :----- | :------------------------------------- | +| host | 是 | - | 地址 | +| port | 是 | - | 端口 | +| db | 否 | 0 | 数据库 | +| pass | 否 | - | 授权密码 | +| maxIdle | 否 | 0 | 允许限制的连接数(0表示不闲置) | +| maxActive | 否 | 0 | 最大连接数量限制(0表示不限制) | +| idleTimeout | 否 | 60 | 连接最大空闲时间(单位秒,不允许设置为0) | +| maxConnLifetime | 否 | 60 | 连接最长存活时间(单位秒,不允许设置为0) | + +其中的`default`和`cache`分别表示配置分组名称,我们在程序中可以通过该名称获取对应配置的redis对象。不传递分组名称时,默认使用`redis.default`配置分组项)来获取对应配置的redis客户端单例对象。 + +## 三、结果处理 + +可以看到通过客户端方法`Do/Receive`获取的数据都是二进制形式`[]byte`的,需要开发者手动进行数据转换。 + +当然,`gredis`模块也提供了`DoVar/ReceiveVar`方法,用以获取可供方便转换的`gvar.Var`通用变量结果。 + +通过`gvar.Var`的强大转换功能可以转换为任意的数据类型,如基本数据类型`Int`,`String`,`Strings`,或者结构体`Struct`等等。 + +## 四、示例 + +### 目录结构 + +```bash +D:. +│ go.mod +│ go.sum +│ main.go +│ +└─config + config.toml +``` + +### main.go + +```go +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/util/gconv" +) + +func main() { + // redis字符串操作 + g.Redis().Do("SET", "k", "v") + v, _ := g.Redis().Do("GET", "k") + g.Log().Info(gconv.String(v)) + + // 获取cache链接 + v2, _ := g.Redis("cache").Do("GET", "k") + g.Log().Info(gconv.String(v2)) + + // DoVar转换 + v3, _ := g.Redis().DoVar("GET", "k") + g.Log().Info(v3.String()) + + // setex + g.Redis().Do("SETEX", "keyEx", 2000, "v4") + v4, _ := g.Redis().DoVar("GET", "keyEx") + g.Log().Info(v4.String()) + + // list + g.Redis().Do("RPUSH", "keyList", "v5") + v5, _ := g.Redis().DoVar("LPOP", "keyList") + g.Log().Info(v5.String()) + + // hash + g.Redis().Do("HSET", "keyHash", "v1", "v6") + v6, _ := g.Redis().DoVar("HGET", "keyHash", "v1") + g.Log().Info(v6.String()) + + // set + g.Redis().Do("SADD", "keySet", "v7") + v7, _ := g.Redis().DoVar("SPOP", "keySet") + g.Log().Info(v7.String()) + + // sort set + g.Redis().Do("ZADD", "keySortSet", 1, "v8") + v8, _ := g.Redis().DoVar("ZREM", "keySortSet", "v8") + g.Log().Info(v8.Int()) + +} +``` + +### config.toml + +```toml +# Redis数据库配置 +[redis] + default = "127.0.0.1:6379,0" + cache = "127.0.0.1:6379,1,123456?idleTimeout=600" +``` + diff --git "a/doc_basic/10.goframe\345\270\270\347\224\250\345\267\245\345\205\267\344\273\213\347\273\215.md" "b/doc_basic/10.goframe\345\270\270\347\224\250\345\267\245\345\205\267\344\273\213\347\273\215.md" new file mode 100644 index 0000000..09cee79 --- /dev/null +++ "b/doc_basic/10.goframe\345\270\270\347\224\250\345\267\245\345\205\267\344\273\213\347\273\215.md" @@ -0,0 +1,254 @@ +# Goframe常用工具介绍 + +## 一、gstr字符串处理 + +字符串处理工具类 + +### 示例 + +```go + p := fmt.Println + p("Contains: ", gstr.Contains("test", "es")) + p("Count: ", gstr.Count("test", "t")) + p("HasPrefix: ", gstr.HasPrefix("test", "te")) + p("HasSuffix: ", gstr.HasSuffix("test", "st")) + p("Join: ", gstr.Join([]string{"a", "b"}, "-")) + p("Repeat: ", gstr.Repeat("a", 5)) + p("Replace: ", gstr.Replace("foo", "o", "0", -1)) + p("Replace: ", gstr.Replace("foo", "o", "0", 1)) + p("Split: ", gstr.Split("a-b-c-d-e", "-")) + p("ToLower: ", gstr.ToLower("TEST")) + p("ToUpper: ", gstr.ToUpper("test")) + p("Trim: ", gstr.Trim(" test ")) +``` + +## 二、g.Map和gmap + +g.Map实现type Map = map[string]interface{} + + + +支持并发安全开关选项的`map`容器,最常用的数据结构。 + +该模块包含多个数据结构的`map`容器:`HashMap`、`TreeMap`和`ListMap`。 + +| 类型 | 数据结构 | 平均复杂度 | 支持排序 | 有序遍历 | 说明 | +| :-------- | :-------------- | :--------- | :------- | :------- | :------------------------------------- | +| `HashMap` | 哈希表 | O(1) | 否 | 否 | 高性能读写操作,内存占用较高,随机遍历 | +| `ListMap` | 哈希表+双向链表 | O(2) | 否 | 是 | 支持按照写入顺序遍历,内存占用较高 | +| `TreeMap` | 红黑树 | O(log N) | 是 | 是 | 内存占用紧凑,支持键名排序及有序遍历 | + +> 此外,`gmap`模块支持多种以哈希表为基础数据结构的常见类型`map`定义:`IntIntMap`、`IntStrMap`、`IntAnyMap`、`StrIntMap`、`StrStrMap`、`StrAnyMap`。 + +**使用场景**: + +任何`map`/哈希表/关联数组使用场景,尤其是并发安全场景中。 + +### 示例 + +```go + // 常规map方法 + p := fmt.Println + // 初始化 + m2 := g.Map{"a": 1, "b": 2} + p(m2) + // 设置 + m2["c"] = 25 + p(m2) + // 获取 + p(m2["b"]) + // 删除 + delete(m2, "c") + // 遍历 + for k, v := range m2 { + p(k, v) + } + + p("###########################") + + // 创建一个默认的gmap对象, + // 默认情况下该gmap对象不支持并发安全特性, + // 初始化时可以给定true参数开启并发安全特性。 + m := gmap.New() + // 设置键值对 + for i := 0; i < 10; i++ { + m.Set(i, i) + } + // 查询大小 + fmt.Println(m.Size()) + // 批量设置键值对(不同的数据类型对象参数不同) + m.Sets(map[interface{}]interface{}{ + 10: 10, + 11: 11, + }) + fmt.Println(m.Size()) + // 查询是否存在 + fmt.Println(m.Contains(1)) + // 查询键值 + fmt.Println(m.Get(1)) + // 删除数据项 + m.Remove(9) + fmt.Println(m.Size()) + // 批量删除 + m.Removes([]interface{}{10, 11}) + fmt.Println(m.Size()) + // 当前键名列表(随机排序) + fmt.Println(m.Keys()) + // 当前键值列表(随机排序) + fmt.Println(m.Values()) + // 查询键名,当键值不存在时,写入给定的默认值 + fmt.Println(m.GetOrSet(100, 100)) + // 删除键值对,并返回对应的键值 + fmt.Println(m.Remove(100)) + // 遍历map + m.Iterator(func(k interface{}, v interface{}) bool { + fmt.Printf("%v:%v ", k, v) + return true + }) + // 清空map + m.Clear() + // 判断map是否为空 + fmt.Println(m.IsEmpty()) +``` + +## 三、gjson + +`gjson`模块实现了强大的`JSON`编码/解析,支持数据层级检索、动态创建修改`Json`对象,并支持常见数据格式的解析和转换等特点。 + +### 示例 + +```go + // 创建json + jsonContent := `{"name":"john", "score":"100"}` + j := gjson.New(jsonContent) + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) + + // 创建json + j2 := gjson.New(nil) + j2.Set("name", "John") + j2.Set("score", 99.5) + fmt.Printf( + "Name: %s, Score: %v\n", + j2.GetString("name"), + j2.GetFloat32("score"), + ) + fmt.Println(j2.MustToJsonString()) + + // struct转json + type Me struct { + Name string `json:"name"` + Score int `json:"score"` + } + me := Me{ + Name: "john", + Score: 100, + } + j3 := gjson.New(me) + fmt.Println(j3.Get("name")) + fmt.Println(j3.Get("score")) + // 转换回Struct + Me2 := new(Me) + if err := j.ToStruct(Me2); err != nil { + panic(err) + } + fmt.Printf(`%+v`, Me2) + fmt.Println() + + // 格式转换 + fmt.Println("JSON:") + fmt.Println(j3.MustToJsonString()) + fmt.Println("======================") + + fmt.Println("XML:") + fmt.Println(j3.MustToXmlString("document")) + fmt.Println("======================") + + fmt.Println("YAML:") + fmt.Println(j3.MustToYamlString()) + fmt.Println("======================") + + fmt.Println("TOML:") + fmt.Println(j3.MustToTomlString()) +``` + +## 四、gmd5 + +MD5算法 + +### 示例 + +```go + p := fmt.Println + // md5加密 + p(gmd5.MustEncrypt("123456")) +``` + +## 五、类型转换 + +### gconv + +`gf`框架提供了非常强大的类型转换包`gconv`,可以实现将任何数据类型转换为指定的数据类型,对常用基本数据类型之间的无缝转换,同时也支持任意类型到`struct`对象的转换。由于`gconv`模块内部大量使用了断言而非反射(仅`struct`转换使用到了反射),因此执行的效率非常高。 + +### Map转换 + +`gconv.Map`支持将任意的`map`或`struct`/`*struct`类型转换为常用的 `map[string]interface{}` 类型。当转换参数为`struct`/`*struct`类型时,支持自动识别`struct`的 `c/gconv/json` 标签,并且可以通过`Map`方法的第二个参数`tags`指定自定义的转换标签,以及多个标签解析的优先级。如果转换失败,返回`nil`。 + +> 属性标签:当转换`struct`/`*struct`类型时, `c/gconv/json` 标签,也支持 `-`及`omitempty` 标签属性。当使用 `-` 标签属性时,表示该属性不执行转换;当使用 `omitempty` 标签属性时,表示当属性为空时(空指针`nil`, 数字`0`, 字符串`""`, 空数组`[]`等)不执行转换。具体请查看随后示例。 + +### Struct转换 + +项目中我们经常会遇到大量`struct`的使用,以及各种数据类型到`struct`的转换/赋值(特别是`json`/`xml`/各种协议编码转换的时候)。为提高编码及项目维护效率,`gconv`模块为各位开发者带来了极大的福利,为数据解析提供了更高的灵活度。 + +### 示例 + +```go + i := 123.456 + fmt.Printf("%10s %v\n", "Int:", gconv.Int(i)) + fmt.Printf("%10s %v\n", "Int8:", gconv.Int8(i)) + fmt.Printf("%10s %v\n", "Int16:", gconv.Int16(i)) + fmt.Printf("%10s %v\n", "Int32:", gconv.Int32(i)) + fmt.Printf("%10s %v\n", "Int64:", gconv.Int64(i)) + fmt.Printf("%10s %v\n", "Uint:", gconv.Uint(i)) + fmt.Printf("%10s %v\n", "Uint8:", gconv.Uint8(i)) + fmt.Printf("%10s %v\n", "Uint16:", gconv.Uint16(i)) + fmt.Printf("%10s %v\n", "Uint32:", gconv.Uint32(i)) + fmt.Printf("%10s %v\n", "Uint64:", gconv.Uint64(i)) + fmt.Printf("%10s %v\n", "Float32:", gconv.Float32(i)) + fmt.Printf("%10s %v\n", "Float64:", gconv.Float64(i)) + fmt.Printf("%10s %v\n", "Bool:", gconv.Bool(i)) + fmt.Printf("%10s %v\n", "String:", gconv.String(i)) + + fmt.Printf("%10s %v\n", "Bytes:", gconv.Bytes(i)) + fmt.Printf("%10s %v\n", "Strings:", gconv.Strings(i)) + fmt.Printf("%10s %v\n", "Ints:", gconv.Ints(i)) + fmt.Printf("%10s %v\n", "Floats:", gconv.Floats(i)) + fmt.Printf("%10s %v\n", "Interfaces:", gconv.Interfaces(i)) + + fmt.Println("##############") + // struct和map转换 + type User struct { + Uid int `c:"uid"` + Name string `c:"name"` + } + // 对象 + m := gconv.Map(User{ + Uid : 1, + Name : "john", + }) + fmt.Println(m) + + fmt.Println("##############") + user := (*User)(nil) + err := gconv.Struct(m, &user) + if err != nil { + panic(err) + } + g.Dump(user) +``` + + +## 六、GF交流 + +- QQ交流群:[116707870](https://shang.qq.com/wpa/qunwpa?idkey=195f91eceeb5d7fa76009b7cd5a4641f70bf4897b7f5a520635eb26ff17adfe7) +- WX交流群:微信添加`389961817`备注`GF`加群 \ No newline at end of file diff --git a/doc_basic/ReadMe.md b/doc_basic/ReadMe.md new file mode 100644 index 0000000..7acb54f --- /dev/null +++ b/doc_basic/ReadMe.md @@ -0,0 +1,14 @@ +# GoFrame基础教程-快速入门 + +## 教程目录 + +* [01.GoFrame介绍.md](01.goframe介绍.md) +* [02.GoFrame基础环境搭建.md](02.goframe基础环境搭建.md) +* [03.GoFrame的WEB服务介绍.md](03.goframe的WEB服务介绍.md) +* [04.GoFrame路由注册.md](04.goframe路由注册.md) +* [05.GoFrame的HTTP客户端.md](05.goframe的HTTP客户端.md) +* [06.GoFrame配置文件.md](06.goframe配置文件.md) +* [07.GoFrame日志打印.md](07.goframe日志打印.md) +* [08.GoFrame数据库操作.md](08.goframe数据库操作.md) +* [09.GoFrameRedis操作.md](09.goframeRedis操作.md) +* [10.GoFrame常用工具介绍.md](10.goframe常用工具介绍.md) diff --git "a/doc_gf_tool_chain/17.GoFrame\345\267\245\345\205\267\351\223\276\344\271\213\345\237\272\346\234\254\344\273\213\347\273\215.md" "b/doc_gf_tool_chain/17.GoFrame\345\267\245\345\205\267\351\223\276\344\271\213\345\237\272\346\234\254\344\273\213\347\273\215.md" new file mode 100644 index 0000000..cefbc3e --- /dev/null +++ "b/doc_gf_tool_chain/17.GoFrame\345\267\245\345\205\267\351\223\276\344\271\213\345\237\272\346\234\254\344\273\213\347\273\215.md" @@ -0,0 +1,112 @@ +# GoFrame工具链之基本介绍 + +## GF工具链介绍 + +GF为GoFrame辅助工具链,地址:https://github.com/gogf/gf-cli;下载对应平台 + +```bash +D:\17.gfcli>gf -h +USAGE + gf COMMAND [ARGUMENT] [OPTION] + +COMMAND + get install or update GF to system in default... + gen automatically generate go files for ORM models... + run running go codes with hot-compiled-like feature... + init initialize an empty GF project at current working directory... + help show more information about a specified command + pack packing any file/directory to a resource file, or a go file + build cross-building go project for lots of platforms... + docker create a docker image for current GF project... + swagger parse and start a swagger feature server for current project... + update update current gf binary to latest one (might need root/admin permission) + install install gf binary to system (might need root/admin permission) + version show current binary version info + +OPTION + -y all yes for all command without prompt ask + -?,-h show this help or detail for specified command + -v,-i show version information + +ADDITIONAL + Use 'gf help COMMAND' or 'gf COMMAND -h' for detail about a command, which has '...' + in the tail of their comments. +``` + +install 安装 + +version 查看版本 + +update 更新 + +init 初始化项目 + +get 安装和更新包 + +run 热编译,自动编译 + +build 交叉编译 + +gen 自动生成,现在主要是生成model + +pack 打成二进制包 + +docker 生成docker文件 + +swagger 解析和开始swagger + +help 帮助 + +## 安装install + +```bash +D:\>dir +2020/04/26 23:02 21,447,168 gf.exe + +D:\>gf install +I found some installable paths for you: + Id | Writable | Installed | Path + 0 | true | false | D:\develop\go\bin + 1 | true | false | D:\Program Files (x86)\NetSarang\Xshell 6\ + 3 | true | false | D:\Program Files\Git\cmd + 5 | true | false | C:\Users\FLY的狐狸\AppData\Local\Microsoft\WindowsApps + 6 | true | false | D:\Program Files\Microsoft VS Code\bin + 7 | true | false | D:\Program Files\Fiddler + 8 | true | false | D:\develop\gopath\bin +please choose one installation destination [default 0]: 0 +gf binary is successfully installed to: D:\develop\go\bin + +``` + +## 版本version和更新update + +```bash +D:\17.gfcli> gf version +GoFrame CLI Tool v0.7.1, https://goframe.org +Install Path: D:\develop\go\bin\gf.exe +Build Detail: + Go Version: go1.14 + GF Version: v1.12.1 + Git Commit: 76483c62719736c36992edb7e4cea92c01ca6fc5 + Build Time: 2020-04-01 21:46:21 + +D:\17.gfcli> gf update +checking... +downloading... +installing... +gf binary is now updated to the latest version + +D:\17.gfcli> gf version +GoFrame CLI Tool v0.7.3, https://goframe.org +Install Path: D:\develop\go\bin\gf.exe +Build Detail: + Go Version: go1.14 + GF Version: v1.12.1 + Git Commit: bd19f7af64f9d34fac2d4d10043ff8020a1ec74a + Build Time: 2020-04-18 14:41:58 + +D:\17.gfcli>gf update +checking... +it's the latest version, no need updates +``` + diff --git "a/doc_gf_tool_chain/18.GoFrame\345\267\245\345\205\267\351\223\276\344\271\213\351\241\271\347\233\256\346\236\204\345\273\272.md" "b/doc_gf_tool_chain/18.GoFrame\345\267\245\345\205\267\351\223\276\344\271\213\351\241\271\347\233\256\346\236\204\345\273\272.md" new file mode 100644 index 0000000..e63d07d --- /dev/null +++ "b/doc_gf_tool_chain/18.GoFrame\345\267\245\345\205\267\351\223\276\344\271\213\351\241\271\347\233\256\346\236\204\345\273\272.md" @@ -0,0 +1,243 @@ +# GoFrame工具链之项目构建 + +## 项目结构 + +推荐的`Go`业务型项目目录结构如下: + +```undefined +/ +├── app +│ ├── api +│ ├── model +│ └── service +├── boot +├── config +├── docker +├── document +├── i18n +├── library +├── public +├── router +├── template +├── vendor +├── Dockerfile +├── go.mod +└── main.go +``` + +| 目录/文件名称 | 说明 | 描述 | +| :------------ | :--------- | :----------------------------------------------------------- | +| `app` | 业务逻辑层 | 所有的业务逻辑存放目录。 | +| - `api` | 业务接口 | 接收/解析用户输入参数的入口/接口层。 | +| - `model` | 数据模型 | 数据管理层,仅用于操作管理数据,如数据库操作。 | +| - `service` | 逻辑封装 | 业务逻辑封装层,实现特定的业务需求,可供不同的包调用。 | +| `boot` | 初始化包 | 用于项目初始化参数设置,往往作为`main.go`中第一个被`import`的包。 | +| `config` | 配置管理 | 所有的配置文件存放目录。 | +| `docker` | 镜像文件 | Docker镜像相关依赖文件,脚本文件等等。 | +| `document` | 项目文档 | Document项目文档,如: 设计文档、帮助文档等等。 | +| `i18n` | I18N国际化 | I18N国际化配置文件目录。 | +| `library` | 公共库包 | 公共的功能封装包,往往不包含业务需求实现。 | +| `public` | 静态目录 | 仅有该目录下的文件才能对外提供静态服务访问。 | +| `router` | 路由注册 | 用于路由统一的注册管理。 | +| `template` | 模板文件 | MVC模板文件存放的目录。 | +| `vendor` | 第三方包 | 第三方依赖包存放目录(可选, 未来会被淘汰)。 | +| `Dockerfile` | 镜像描述 | 云原生时代用于编译生成Docker镜像的描述文件。 | +| `go.mod` | 依赖管理 | 使用`Go Module`包管理的依赖描述文件。 | +| `main.go` | 入口文件 | 程序入口文件。 | + +在实践中,小伙伴们可以根据实际情况增删目录。 + +## 初始化项目init + +```bash +D:\17.gfcli>gf init -h +USAGE + gf init [NAME] + +ARGUMENT + NAME name for current project, not necessary, default name is 'gf-app' + +EXAMPLES + gf init + gf init my-project-name + + +D:\17.gfcli>gf init gfcli +initializing... +initialization done! +you can now run 'gf run main.go' to start your journey, enjoy! + +D:\17.gfcli> tree /f +卷 Data 的文件夹 PATH 列表 +卷序列号为 DA91-D877 +D:. +│ .gitattributes +│ .gitignore +│ Dockerfile +│ go.mod +│ go.sum +│ main.go +│ README.MD +│ +├─app +│ ├─api +│ │ └─hello +│ │ hello.go +│ │ +│ ├─model +│ │ .gitkeep +│ │ +│ └─service +│ .gitkeep +│ +├─boot +│ .gitkeep +│ boot.go +│ +├─config +│ .gitkeep +│ config.toml +│ +├─docker +│ .gitkeep +│ +├─document +│ .gitkeep +│ +├─i18n +│ .gitkeep +│ +├─public +│ ├─html +│ │ .gitkeep +│ │ +│ ├─plugin +│ │ .gitkeep +│ │ +│ └─resource +│ ├─css +│ │ .gitkeep +│ │ +│ ├─image +│ │ .gitkeep +│ │ +│ └─js +│ .gitkeep +│ +├─router +│ .gitkeep +│ router.go +│ +└─template + .gitkeep +``` + +## 热编译运行项目run + +```bash +D:\17.gfcli> go install +go: downloading github.com/gogf/gf v1.12.2 +go: downloading gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c +go: downloading github.com/mattn/go-runewidth v0.0.9 + +D:\17.gfcli> gf run .\main.go +2020-04-26 22:58:09.022 [DEBU] [ghttp] SetServerRoot path: D:\17.gfcli\public + + SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +|---------|---------|---------|--------|-------|---------------------------|------------| + default | default | :8199 | ALL | / | gfcli/app/api/hello.Hello | +|---------|---------|---------|--------|-------|---------------------------|------------| + +2020-04-26 22:58:09.041 16764: http server started listening on [:8199] +exit status 2 +``` + +## 交叉编译build + +```bash +D:\17.gfcli> gf build -h +USAGE + gf build FILE [OPTION] + +ARGUMENT + FILE building file path. + +OPTION + -n, --name output binary name + -v, --version output binary version + -a, --arch output binary architecture, multiple arch separated with ',' + -s, --system output binary system, multiple os separated with ',' + -o, --output output binary path, used when building single binary file + -p, --path output binary directory path, default is './bin' + -e, --extra extra custom "go build" options + -m, --mod like "-mod" option of "go build", use "-m none" to disable go module + --swagger auto parse and pack swagger into boot/data-swagger.go before building. + --pack auto pack config,public,template folder into boot/data-packed.go before building. + +EXAMPLES + gf build main.go + gf build main.go --swagger + gf build main.go --pack + gf build main.go -m none --pack + gf build main.go -n my-app -a all -s all + gf build main.go -n my-app -a amd64,386 -s linux -p . + gf build main.go -n my-app -v 1.0 -a amd64,386 -s linux,windows,darwin -p ./docker/bin + +DESCRIPTION + The "build" command is most commonly used command, which is designed as a powerful wrapper for + "go build" command for convenience cross-compiling usage. + It provides much more features for building binary: + 1. Cross-Compiling for many platforms and architectures. + 2. Configuration file support for compiling. + 3. Build-In Variables. + +PLATFORMS + darwin 386 + darwin amd64 + freebsd 386 + freebsd amd64 + freebsd arm + linux 386 + linux amd64 + linux arm + linux arm64 + linux ppc64 + linux ppc64le + linux mips + linux mipsle + linux mips64 + linux mips64le + netbsd 386 + netbsd amd64 + netbsd arm + openbsd 386 + openbsd amd64 + openbsd arm + windows 386 + windows amd64 + +D:\17.gfcli> gf build main.go -n my-app -a amd64,386 -s linux,windows +2020-04-27 00:29:56.789 start building... +2020-04-27 00:29:56.790 go build -o ./bin/linux_386/my-app main.go +2020-04-27 00:30:00.745 go build -o ./bin/linux_amd64/my-app main.go +2020-04-27 00:30:04.317 go build -o ./bin/windows_386/my-app.exe main.go +2020-04-27 00:30:08.286 go build -o ./bin/windows_amd64/my-app.exe main.go +2020-04-27 00:30:11.449 done! + +D:\17.gfcli> tree /f .\bin +卷 Data 的文件夹 PATH 列表 +卷序列号为 DA91-D877 +D:\17.GFCLI\BIN +├─linux_386 +│ my-app +│ +├─linux_amd64 +│ my-app +│ +├─windows_386 +│ my-app.exe +│ +└─windows_amd64 + my-app.exe +``` + diff --git "a/doc_gf_tool_chain/19.GoFrame\345\267\245\345\205\267\351\223\276\344\271\213\344\273\243\347\240\201\347\224\237\346\210\220.md" "b/doc_gf_tool_chain/19.GoFrame\345\267\245\345\205\267\351\223\276\344\271\213\344\273\243\347\240\201\347\224\237\346\210\220.md" new file mode 100644 index 0000000..160a88c --- /dev/null +++ "b/doc_gf_tool_chain/19.GoFrame\345\267\245\345\205\267\351\223\276\344\271\213\344\273\243\347\240\201\347\224\237\346\210\220.md" @@ -0,0 +1,58 @@ +# GoFrame工具链之代码生成 + +### 代码生成gen之model生成 + +现在gen命令主要是生成model + +模型生成采用了`Active Record`设计模式。该命令将会根据数据表名生成对应的目录,该目录名称即数据表包名。目录下自动生成3个文件: + +1. `数据表名.go` 自定义文件,开发者可以自由定义填充的代码文件,仅会生成一次,每一次模型生成不会覆盖。 +2. `数据表名_entity.go` 表结构文件,根据数据表结构生成的结构体定义文件,包含字段注释。数据表在外部变更后,可使用`gen`命令重复生成更新该文件。 +3. `数据表名_model.go` 表模型文件,为数据表提供了许多便捷的`CURD`操作方法,并可直接查询返回该表的结构体对象。数据表在外部变更后,可使用`gen`命令重复生成更新该文件。 + +```bash +D:\17.gfcli> gf gen -h +USAGE + gf gen model [PATH] [OPTION] + +ARGUMENT + PATH the destination for storing generated files, not necessary, default is "./app/model" + +OPTION + -l, --link database configuration, please refer to: https://goframe.org/database/gdb/config + -t, --table generate models only for given tables, multiple table names separated with ',' + -g, --group used with "-c" option, specifying the configuration group name for database, + it's not necessary and the default value is "default" + -c, --config used to specify the configuration file for database, it's commonly not necessary. + If "-l" is not passed, it will search "./config.toml" and "./config/config.toml" + in current working directory in default. + -p, --prefix remove specified prefix of the table, multiple prefix separated with ',' + + +EXAMPLES + gf gen model + gf gen model -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + gf gen model ./model -l "mssql:sqlserver://sa:12345678@127.0.0.1:1433?database=test" + gf gen model ./model -c config.yaml -g user-center -t user,user_detail,user_login + gf gen model -p user_,p_ + +DESCRIPTION + The "gen" command is designed for multiple generating purposes. + It's currently supporting generating go files for ORM models. + +PS D:\17.gfcli> gf gen model ./model -c config/config.toml -p sys_ -t sys_user +2020-04-26 23:35:31.682 [DEBU] [ 51 ms] SHOW FULL COLUMNS FROM `sys_user` +generated: ./model\user\user.go +generated: ./model\user\user_entity.go +generated: ./model\user\user_model.go +done! + +D:\17.gfcli> tree /f .\model +卷 Data 的文件夹 PATH 列表 +卷序列号为 DA91-D877 +D:\17.GFCLI\MODEL +└─user + user.go + user_entity.go + user_model.go +``` \ No newline at end of file diff --git "a/doc_gf_tool_chain/20.GoFrame\345\267\245\345\205\267\351\223\276\344\271\213\345\205\266\344\273\226\345\221\275\344\273\244.md" "b/doc_gf_tool_chain/20.GoFrame\345\267\245\345\205\267\351\223\276\344\271\213\345\205\266\344\273\226\345\221\275\344\273\244.md" new file mode 100644 index 0000000..0b08163 --- /dev/null +++ "b/doc_gf_tool_chain/20.GoFrame\345\267\245\345\205\267\351\223\276\344\271\213\345\205\266\344\273\226\345\221\275\344\273\244.md" @@ -0,0 +1,159 @@ +# GoFrame工具链之其他命令 + +## 获取包get + +go.mod + +```bash +module gfcli + +go 1.13 +``` + +获取包 + +```bash +$ gf get github.com/gogf/gf +go: github.com/gogf/gf upgrade => v1.12.2 +``` + +go.mod + +```bahs +module gfcli + +require github.com/gogf/gf v1.12.2 // indirect + +go 1.13 +``` + +## 打二进制包pack + +```bash +$ gf pack -h +USAGE + gf pack SRC DST + +ARGUMENT + SRC source path for packing, which can be multiple source paths. + DST destination file path for packed file. if extension of the filename is ".go" and "-n" option is given, + it enables packing SRC to go file, or else it packs SRC into a binary file. + +OPTION + -n, --name package name for output go file + -p, --prefix prefix for each file packed into the resource file + +EXAMPLES + gf pack public data.bin + gf pack public,template data.bin + gf pack public,template boot/data.go -n=boot + gf pack public,template,config resource/resource.go -n=resource + gf pack public,template,config resource/resource.go -n=resource -p=/var/www/my-app + gf pack /var/www/public resource/resource.go -n=resource + + $ gf pack config,public,template boot/data.go -n boot +done! +``` + +生成文件data.go,内容省略 + +```bash +package boot + +import "github.com/gogf/gf/os/gres" + +func init() { + if err := gres.Add("1f8b0800000000000"); err != nil { + panic(err) + } +} +``` + +通过gres.Dump()打印 + +```bash +2020-04-28T17:06:23+00:00 0.00B config +2020-04-28T16:13:03+00:00 0.00B config/.gitkeep +2020-04-28T16:35:35+00:00 578.00B config/config.toml +2020-04-28T17:06:23+00:00 0.00B public +2020-04-28T16:13:03+00:00 0.00B public/html +2020-04-28T16:13:03+00:00 0.00B public/html/.gitkeep +2020-04-28T16:13:03+00:00 0.00B public/plugin +2020-04-28T16:13:03+00:00 0.00B public/plugin/.gitkeep +2020-04-28T16:13:03+00:00 0.00B public/resource +2020-04-28T16:13:03+00:00 0.00B public/resource/css +2020-04-28T16:13:03+00:00 0.00B public/resource/css/.gitkeep +2020-04-28T16:13:03+00:00 0.00B public/resource/image +2020-04-28T16:13:03+00:00 0.00B public/resource/image/.gitkeep +2020-04-28T16:13:03+00:00 0.00B public/resource/js +2020-04-28T16:13:03+00:00 0.00B public/resource/js/.gitkeep +2020-04-28T17:06:23+00:00 0.00B template +2020-04-28T16:13:03+00:00 0.00B template/.gitkeep +``` + +## 生成Dockerfile + +```bash +$ gf docker -h +USAGE + gf docker [FILE] [OPTION] + +ARGUMENT + FILE file path for "gf build", it's "main.go" in default. + OPTION the same options as "docker build" except some options as follows defined + +OPTION + -p, --push auto push the docker image to docker registry if "-t" option passed + +EXAMPLES + gf docker + gf docker -t hub.docker.com/john/image:tag + gf docker -p -t hub.docker.com/john/image:tag + gf docker main.go + gf docker main.go -t hub.docker.com/john/image:tag + gf docker main.go -t hub.docker.com/john/image:tag + gf docker main.go -p -t hub.docker.com/john/image:tag + +DESCRIPTION + The "docker" command builds the GF project to a docker images. It runs "docker build" + command automatically, so you should have docker command first. + There must be a Dockerfile in the root of the project. + +$gf docker main.go -p -t 10.130.44.133/test/gfcli:v1.0.0 +2020-04-29 00:57:54.378 start building... +2020-04-29 00:57:54.379 go build -o ./bin/linux_amd64/main main.go +2020-04-29 00:57:55.849 done! +2020-04-29 00:57:55.943 docker build . +2020-04-29 00:57:56.831 docker push 10.130.44.133/test/gfcli:v1.0.0 +``` + +## 生成swagger文档 + +```bash +$ gf swagger -h +USAGE + gf swagger [OPTION] + +OPTION + -s, --server start a swagger server at specified address after swagger files + produced + -o, --output the output directory for storage parsed swagger files, + the default output directory is "./swagger" + -/--pack auto parses and packs swagger into boot/data-swagger.go. + +EXAMPLES + gf swagger + gf swagger --pack + gf swagger -s 8080 + gf swagger -s 127.0.0.1:8080 + gf swagger -o ./document/swagger + + +DESCRIPTION + The "swagger" command parses the current project and produces swagger API description + files, which can be used in swagger API server. If used with "-s/--server" option, it + watches the changes of go files of current project and reproduces the swagger files, + which is quite convenient for local API development. + +``` + diff --git a/doc_gf_tool_chain/ReadMe.md b/doc_gf_tool_chain/ReadMe.md new file mode 100644 index 0000000..2c5182d --- /dev/null +++ b/doc_gf_tool_chain/ReadMe.md @@ -0,0 +1,8 @@ +# GoFrame工具链 + +## 教程目录 + +* [17.GoFrame工具链之基本介绍](17.GoFrame工具链之基本介绍.md) +* [18.GoFrame工具链之项目构建](18.GoFrame工具链之项目构建.md) +* [19.GoFrame工具链之代码生成](19.GoFrame工具链之代码生成.md) +* [20.GoFrame工具链之其他命令](20.GoFrame工具链之其他命令.md) diff --git "a/doc_login/11.GoFrame\347\231\273\345\275\225\345\256\236\346\210\230\344\271\213\346\250\241\346\235\277\345\274\225\346\223\216.md" "b/doc_login/11.GoFrame\347\231\273\345\275\225\345\256\236\346\210\230\344\271\213\346\250\241\346\235\277\345\274\225\346\223\216.md" new file mode 100644 index 0000000..793d1bf --- /dev/null +++ "b/doc_login/11.GoFrame\347\231\273\345\275\225\345\256\236\346\210\230\344\271\213\346\250\241\346\235\277\345\274\225\346\223\216.md" @@ -0,0 +1,268 @@ +# GoFrame登录实战之模板引擎 + +这节课开始除了会介绍一部分GoFrame基础知识,也有一些关键的知识点和实战经验进行分享。示例还是主要以GoFrame为基础; + +实践是检验真理的唯一标准。希望大家可以多跟练习,多去思考,多去体会,而不是简单的听; + +## 一、模板引擎 + +模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档。 + +但模板引擎不属于特定技术领域,它是跨领域跨平台的概念。 + +模板配置config.toml + +```toml +# 模板引擎配置 +[viewer] + Path = "template" + DefaultFile = "index.html" + Delimiters = ["${", "}"] +``` + +模板使用 + +```go +// 调用文件 +s := g.Server() +s.BindHandler("/template", func(r *ghttp.Request) { + r.Response.WriteTpl("index.tpl", g.Map{ + "id": 123, + "name": "john", + }) +}) +// 直接传入字符串 +s := g.Server() +s.BindHandler("/template", func(r *ghttp.Request){ + tplContent := `id:{{.id}}, name:{{.name}}` + r.Response.WriteTplContent(tplContent, g.Map{ + "id" : 123, + "name" : "john", + }) +}) +``` + +模板常用标签 + +```html + +{{ .value }} + +{{if .condition}} + ... +{{else if .condition2}} + ... +{{else}} + ... +{{end}} + +{{range $index, $elem := .SliceContent}} + {{range $key, $value := $elem}} + {{$key}}:{{$value}} + {{end}} +{{end}} + +{{include "模板文件名(需要带完整文件名后缀)" .}} + +{{/* +comment content +support new line +*/}} +``` + +模板也支持函数,大家也可以自定义函数 + +```html +${"我是中国人" | substr 2 -1} +``` + +其实模板可以当做一种语言来讲,这里不做过多介绍,一般使用模板只用一些基本功能,但是要想深入了解建议去看go模板和GoFram官网模板章节; + +## 二、示例 + +### 目录 + +```bash +:. +│ go.mod +│ go.sum +│ main.go +│ +├─config +│ config.toml +│ +└─template + index.html + include.html +``` + +### config.toml + +```toml +# 模板引擎配置 +[viewer] + Path = "template" + DefaultFile = "index.html" + Delimiters = ["${", "}"] +``` + +### go.mod + +```bash +module gf-template + +go 1.14 + +require github.com/gogf/gf v1.12.1 +``` + +### main.go + +```go +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +func main() { + s := g.Server() + // 常规注册 + group := s.Group("/") + + // 模板文件 + group.GET("/", func(r *ghttp.Request) { + r.Response.WriteTpl("index.html", g.Map{ + "title": "列表页面", + "show": true, + "listData": g.List{ + g.Map{ + "date": "2020-04-01", + "name": "朱元璋", + "address": "江苏110号", + }, + g.Map{ + "date": "2020-04-02", + "name": "徐达", + "address": "江苏111号", + }, + g.Map{ + "date": "2020-04-03", + "name": "李善长", + "address": "江苏112号", + }, + }}) + }) + + // 字符串传入 + group.GET("/template", func(r *ghttp.Request) { + tplContent := `id:${.id}, name:${.name}` + r.Response.WriteTplContent(tplContent, g.Map{ + "id" : 123, + "name" : "john", + }) + }) + + s.Run() +} + +``` + +### index.html + +```html + + + + + + + + + +
+ + + ${ .title } + ${if .show}【展示】${end} + + + + + + + + + ${include "include.html" .} + +
+ + + + + + + + + + +``` + +### include.html + +```bash + + + 这里是通过include引用的文件内容 + + +``` diff --git "a/doc_login/12.GoFrame\347\231\273\345\275\225\345\256\236\346\210\230\344\271\213\347\231\273\345\275\225\346\265\201\347\250\213.md" "b/doc_login/12.GoFrame\347\231\273\345\275\225\345\256\236\346\210\230\344\271\213\347\231\273\345\275\225\346\265\201\347\250\213.md" new file mode 100644 index 0000000..c53f010 --- /dev/null +++ "b/doc_login/12.GoFrame\347\231\273\345\275\225\345\256\236\346\210\230\344\271\213\347\231\273\345\275\225\346\265\201\347\250\213.md" @@ -0,0 +1,326 @@ +# GoFrame登录实战之登录流程 + +## 一、登录介绍 + +登录简单来说就是客户端输入账号密码,服务端进行账号密码验证,通过可访问系统,不通过停留在登录页面不能访问系统;登录看似简单的流程,但是实际还是有许多知识点是值得我们学习的; + +## 二、登录流程图 + +下面我们来简单介绍一下设计相关的线框图,流程图和时序图;这里我使用的visio,大家也可以使用亿图,在线ProcessOn。 + +### 基本流程图 + +![image-20200413235318994](12.login.assets/image-20200413235318994.png) + +基本流程图主要就是开始结束,流程处理,判断判断,数据,文档等; + +### UML序列 + +![image-20200413235702664](12.login.assets/image-20200413235702664.png) + +UML时序图主要是对象,请求,响应,自关联,循环,可选; + +![image-20200414000541863](12.login.assets/image-20200414000541863.png) + + + +### 示例 + +![image-20200414005122076](12.login.assets/image-20200414005122076.png) + + + +## 三、GoFrame登录示例 + +### 目录结构 + +```bash +:. +│ go.mod +│ go.sum +│ main.go +│ +├─config +│ config.toml +│ +└─template + index.html + user_index.html +``` + +### go.mod + +```bash +module gf-login11 + +go 1.14 + +require github.com/gogf/gf v1.12.1 +``` + +### main.go + +``` +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +func main() { + s := g.Server() + // 常规注册 + group := s.Group("/") + // 登录页面 + group.GET("/", func(r *ghttp.Request) { + r.Response.WriteTpl("index.html", g.Map{ + "title": "登录页面", + }) + }) + // 登录接口 + group.POST("/login", func(r *ghttp.Request) { + username := r.GetString("username") + password := r.GetString("password") + + //dbUsername := "admin" + //dbPassword := "123456" + dbUsername := g.Config().GetString("username") + dbPassword := g.Config().GetString("password") + if username == dbUsername && password == dbPassword { + r.Response.WriteJson(g.Map{ + "code": 0, + "msg": "登录成功", + }) + r.Exit() + } + + r.Response.WriteJson(g.Map{ + "code": -1, + "msg": "登录失败", + }) + }) + // 列表页面 + group.GET("/user/index", func(r *ghttp.Request) { + r.Response.WriteTpl("user_index.html", g.Map{ + "title": "列表页面", + "dataList": g.List{ + g.Map{ + "date": "2020-04-01", + "name": "朱元璋", + "address": "江苏110号", + }, + g.Map{ + "date": "2020-04-02", + "name": "徐达", + "address": "江苏111号", + }, + g.Map{ + "date": "2020-04-03", + "name": "李善长", + "address": "江苏112号", + }, + }}) + }) + // 登出接口 + group.POST("/logout", func(r *ghttp.Request) { + r.Response.WriteJson(g.Map{ + "code": 0, + "msg": "登出成功", + }) + }) + + s.Run() +} +``` + +### 页面index.html + +```html + + + + + + + + + +
+ + + ${ .title } + + + + + + + + + + + + + + + 登录 + + + + +
+ + + + + + + + + + +``` + +### 页面user_index.html + +```html + + + + + + + + + +
+ + + ${ .title } + + + + + + + + + + 登出 + + + + +
+ + + + + + + + + + +``` \ No newline at end of file diff --git a/doc_login/12.login.assets/image-20200413235318994.png b/doc_login/12.login.assets/image-20200413235318994.png new file mode 100644 index 0000000..54dbdb6 Binary files /dev/null and b/doc_login/12.login.assets/image-20200413235318994.png differ diff --git a/doc_login/12.login.assets/image-20200413235702664.png b/doc_login/12.login.assets/image-20200413235702664.png new file mode 100644 index 0000000..debbba5 Binary files /dev/null and b/doc_login/12.login.assets/image-20200413235702664.png differ diff --git a/doc_login/12.login.assets/image-20200414000541863.png b/doc_login/12.login.assets/image-20200414000541863.png new file mode 100644 index 0000000..22caacb Binary files /dev/null and b/doc_login/12.login.assets/image-20200414000541863.png differ diff --git a/doc_login/12.login.assets/image-20200414005122076.png b/doc_login/12.login.assets/image-20200414005122076.png new file mode 100644 index 0000000..df11f61 Binary files /dev/null and b/doc_login/12.login.assets/image-20200414005122076.png differ diff --git "a/doc_login/13.GoFrame\347\231\273\345\275\225\345\256\236\346\210\230\344\271\213cookie\345\222\214session.md" "b/doc_login/13.GoFrame\347\231\273\345\275\225\345\256\236\346\210\230\344\271\213cookie\345\222\214session.md" new file mode 100644 index 0000000..52d0208 --- /dev/null +++ "b/doc_login/13.GoFrame\347\231\273\345\275\225\345\256\236\346\210\230\344\271\213cookie\345\222\214session.md" @@ -0,0 +1,61 @@ +# GoFrame登录实战之cookie&session + +## 一、HTTP + +超文本传输协议(英文:**H**yper**T**ext **T**ransfer **P**rotocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。**http是一个简单的请求-响应协议,它通常运行在TCP之上。HTTP是无状态的。** + +## 二、cookie + +Cookie,有时也用其复数形式 Cookies。类型为“小型文本文件”,是某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息 。 + + + +**Cookie 是一个请求首部,**其中含有先前由服务器通过 Set-Cookie 首部投放并存储到客户端的 HTTP cookies。 + +这个首部可能会被完全移除,例如在浏览器的隐私设置里面设置为禁用cookie。 + +``` +Cookie: +Cookie: name=value +Cookie: name=value; name2=value2; name3=value3 +``` + +响应首部 **`Set-Cookie`** 被用来由服务器端向客户端发送 cookie + +``` +# 设置cookie +Set-Cookie: = +# cookie的最长有效时间 +Set-Cookie: =; Expires= +# 在cookie失效之前需要经过的秒数; +# 假如二者 (指 Expires 和Max-Age) 均存在,那么 Max-Age 优先级更高 +Set-Cookie: =; Max-Age= +# 指定 cookie 可以送达的主机名 +Set-Cookie: =; Domain= +# 指定一个 URL 路径,这个路径必须出现在要请求的资源的路径中才可以发送 Cookie 首部 +Set-Cookie: =; Path= +# 一个带有安全属性的 cookie 只有在请求使用SSL和HTTPS协议的时候才会被发送到服务器 +Set-Cookie: =; Secure +# 设置了 HttpOnly 属性的 cookie 不能使用 JavaScript 经由 Document.cookie 属性、XMLHttpRequest 和 Request APIs 进行访问,以防范跨站脚本攻击(XSS) +Set-Cookie: =; HttpOnly +``` + +## 三、session + +Session:在计算机中,尤其是在网络应用中,称为“会话控制”。 + +Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。 + +## 四、sessionId + +当用户发送请求的时候,服务器将用户cookie里面记录的session_id和服务器内存中存放的session_id进行比对,从而找到用户相对应的session进行操作。 + + 在tomcat中session id中用JSESSIONID来表示; + +## 五、cookie与session关系 + +![image-20200412002802721](13.gsession.assets/image-20200412002802721.png) + +1. 登录页面输入账号密码请求;服务端认证通过,存储session,设置Cookie; +2. 请求资源需要的资源;服务端查看sessionId,判断sessionId是否存在,存在人为已登录返回资源; + diff --git a/doc_login/13.gsession.assets/image-20200412002802721.png b/doc_login/13.gsession.assets/image-20200412002802721.png new file mode 100644 index 0000000..65be36b Binary files /dev/null and b/doc_login/13.gsession.assets/image-20200412002802721.png differ diff --git "a/doc_login/14.GoFrame\347\231\273\345\275\225\345\256\236\346\210\230\344\271\213session\345\256\236\347\216\260.md" "b/doc_login/14.GoFrame\347\231\273\345\275\225\345\256\236\346\210\230\344\271\213session\345\256\236\347\216\260.md" new file mode 100644 index 0000000..bea1b0a --- /dev/null +++ "b/doc_login/14.GoFrame\347\231\273\345\275\225\345\256\236\346\210\230\344\271\213session\345\256\236\347\216\260.md" @@ -0,0 +1,398 @@ +# GoFrame登录实战之session实现 + +## 一、概念介绍 + +`GF`框架提供了完善的`Session`管理能力,由`gsession`模块实现。由于`Session`机制在`HTTP`服务中最常用,因此后续章节中将着重以`HTTP`服务为示例介绍`Session`的使用。 + +## 二、存储实现方式 + +`gsession`实现并为开发者提供了常见的三种`Session`存储实现方式: + +1. 基于文件存储(默认):单节点部署方式下比较高效的持久化存储方式; +2. 基于纯内存存储:性能最高效,但是无法持久化保存,重启即丢失; +3. 基于`Redis`存储:远程`Redis`节点存储`Session`数据,支持应用多节点部署; + +代码: + +```bash +s := g.Server() +// 设置文件 +s.SetConfigWithMap(g.Map{ + "SessionStorage": gsession.NewStorageFile("/tmp"), +}) +// 设置内存 +s.SetConfigWithMap(g.Map{ + "SessionStorage": gsession.NewStorageMemory(), +}) +// 设置redis +s.SetConfigWithMap(g.Map{ + "SessionStorage": gsession.NewStorageRedis(g.Redis()), +}) +``` + +## 三、示例 + +### 目录 + +```bash +:. +│ go.mod +│ go.sum +│ main.go +│ +├─config +│ config.toml +│ +├─gession +│ └─default +│ C1YHTZWK7PS0AEN9VA +│ +├─template +│ index.html +│ user_index.html +│ +└─test + test.http +``` + +### main.go + +```bash +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/os/gsession" +) + +const SessionUser = "SessionUser" + +func main() { + s := g.Server() + + // 设置存储方式 + sessionStorage := g.Config().GetString("SessionStorage") + if sessionStorage == "redis" { + s.SetConfigWithMap(g.Map{ + "SessionIdName": g.Config().GetString("server.SessionIdName"), + "SessionStorage": gsession.NewStorageRedis(g.Redis()), + }) + } else if sessionStorage == "memory" { + s.SetConfigWithMap(g.Map{ + "SessionStorage": gsession.NewStorageMemory(), + }) + } + + // 常规注册 + group := s.Group("/") + group.GET("/", func(r *ghttp.Request) { + r.Response.WriteTpl("index.html", g.Map{ + "title": "登录页面", + }) + }) + group.POST("/login", func(r *ghttp.Request) { + username := r.GetString("username") + password := r.GetString("password") + + //dbUsername := "admin" + //dbPassword := "123456" + dbUsername := g.Config().GetString("username") + dbPassword := g.Config().GetString("password") + if username == dbUsername && password == dbPassword { + // 添加session + r.Session.Set(SessionUser, g.Map{ + "username": dbUsername, + "name": "管理员", + }) + r.Response.WriteJson(g.Map{ + "code": 0, + "msg": "登录成功", + }) + r.Exit() + } + + r.Response.WriteJson(g.Map{ + "code": -1, + "msg": "登录失败", + }) + }) + + // 用户组 + userGroup := s.Group("/user") + userGroup.Middleware(MiddlewareAuth) + // 列表页面 + userGroup.GET("/index", func(r *ghttp.Request) { + r.Response.WriteTpl("user_index.html", g.Map{ + "title": "列表页面", + "dataList": g.List{ + g.Map{ + "date": "2020-04-01", + "name": "朱元璋", + "address": "江苏110号", + }, + g.Map{ + "date": "2020-04-02", + "name": "徐达", + "address": "江苏111号", + }, + g.Map{ + "date": "2020-04-03", + "name": "李善长", + "address": "江苏112号", + }, + }}) + }) + userGroup.POST("/logout", func(r *ghttp.Request) { + // 删除session + r.Session.Remove(SessionUser) + + r.Response.WriteJson(g.Map{ + "code": 0, + "msg": "登出成功", + }) + }) + + s.Run() +} + +// 认证中间件 +func MiddlewareAuth(r *ghttp.Request) { + if r.Session.Contains(SessionUser) { + r.Middleware.Next() + } else { + // 获取用错误码 + r.Response.WriteJson(g.Map{ + "code": 403, + "msg": "您访问超时或已登出", + }) + } +} + +``` + +### config.toml + +```toml +# 账号 +username = "admin" +# 密码 +password = "123456" + +# session存储方式file,memory,redis +# SessionStorage = "file" + +[server] + Address = ":8199" + SessionIdName = "gSessionId" + SessionPath = "./gession" + SessionMaxAge = "1m" + DumpRouterMap = true + +# 模板引擎配置 +[viewer] + Path = "template" + DefaultFile = "index.html" + Delimiters = ["${", "}"] +``` + +### index.html + +```html + + + + + + + + + +
+ + + ${ .title } + + + + + + + + + + + + + + + 登录 + + + + +
+ + + + + + + + + + +``` + +### user_index.html + +```html + + + + + + + + + +
+ + + ${ .title } + + + + + + + + + + 登出 + + + + +
+ + + + + + + + + + +``` + +### test.http + +```bash +POST http://127.0.0.1:8199/user/list +#Cookie: MySessionId=C1YHEOJ167MSBFJ5K1 + +### + +``` diff --git "a/doc_login/15.GoFrame\347\231\273\345\275\225\345\256\236\346\210\230\344\271\213\346\225\260\346\215\256\346\240\241\351\252\214.md" "b/doc_login/15.GoFrame\347\231\273\345\275\225\345\256\236\346\210\230\344\271\213\346\225\260\346\215\256\346\240\241\351\252\214.md" new file mode 100644 index 0000000..055da42 --- /dev/null +++ "b/doc_login/15.GoFrame\347\231\273\345\275\225\345\256\236\346\210\230\344\271\213\346\225\260\346\215\256\346\240\241\351\252\214.md" @@ -0,0 +1,138 @@ +# GoFrame登录实战之数据校验 + +## 一、校验规则 + +`gvalid`模块实现了非常强大的数据校验功能,封装了`40种`常用的校验规则,支持单数据多规则校验、多数据多规则批量校验、自定义错误信息、自定义正则校验、支持`struct tag`规则及提示信息绑定等特性,是目前功能最强大的`Go`数据校验模块。 + +校验规则: + +```ini +required 格式:required 说明:必需参数 +required-if 格式:required-if:field,value,... 说明:必需参数(当任意所给定字段值与所给值相等时,即:当field字段的值为value时,当前验证字段为必须参数) +required-unless 格式:required-unless:field,value,... 说明:必需参数(当所给定字段值与所给值都不相等时,即:当field字段的值不为value时,当前验证字段为必须参数) +required-with 格式:required-with:field1,field2,... 说明:必需参数(当所给定任意字段值不为空时) +required-with-all 格式:required-with-all:field1,field2,... 说明:必须参数(当所给定所有字段值都不为空时) +required-without 格式:required-without:field1,field2,... 说明:必需参数(当所给定任意字段值为空时) +required-without-all 格式:required-without-all:field1,field2,...说明:必须参数(当所给定所有字段值都为空时) +date 格式:date 说明:参数为常用日期类型,格式:2006-01-02, 20060102, 2006.01.02 +date-format 格式:date-format:format 说明:判断日期是否为指定的日期格式,format为Go日期格式(可以包含时间) +email 格式:email 说明:EMAIL邮箱地址 +phone 格式:phone 说明:手机号 +telephone 格式:telephone 说明:国内座机电话号码,"XXXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"、"XXXXXXXX" +passport 格式:passport 说明:通用帐号规则(字母开头,只能包含字母、数字和下划线,长度在6~18之间) +password 格式:password 说明:通用密码(任意可见字符,长度在6~18之间) +password2 格式:password2 说明:中等强度密码(在弱密码的基础上,必须包含大小写字母和数字) +password3 格式:password3 说明:强等强度密码(在弱密码的基础上,必须包含大小写字母、数字和特殊字符) +postcode 格式:postcode 说明:中国邮政编码 +id-number 格式:id-number 说明:公民身份证号码 +luhn 格式:luhn 说明:银行号验证 +qq 格式:qq 说明:腾讯QQ号码 +ip 格式:ip 说明:IPv4/IPv6地址 +ipv4 格式:ipv4 说明:IPv4地址 +ipv6 格式:ipv6 说明:IPv6地址 +mac 格式:mac 说明:MAC地址 +url 格式:url 说明:URL +domain 格式:domain 说明:域名 +length 格式:length:min,max 说明:参数长度为min到max(长度参数为整形),注意中文一个汉字占3字节 +min-length 格式:min-length:min 说明:参数长度最小为min(长度参数为整形),注意中文一个汉字占3字节 +max-length 格式:max-length:max 说明:参数长度最大为max(长度参数为整形),注意中文一个汉字占3字节 +between 格式:between:min,max 说明:参数大小为min到max(支持整形和浮点类型参数) +min 格式:min:min 说明:参数最小为min(支持整形和浮点类型参数) +max 格式:max:max 说明:参数最大为max(支持整形和浮点类型参数) +json 格式:json 说明:判断数据格式为JSON +integer 格式:integer 说明:整数 +float 格式:float 说明:浮点数 +boolean 格式:boolean 说明:布尔值(1,true,on,yes:true | 0,false,off,no,"":false) +same 格式:same:field 说明:参数值必需与field参数的值相同 +different 格式:different:field 说明:参数值不能与field参数的值相同 +in 格式:in:value1,value2,... 说明:参数值应该在value1,value2,...中(字符串匹配) +not-in 格式:not-in:value1,value2,... 说明:参数值不应该在value1,value2,...中(字符串匹配) +regex 格式:regex:pattern 说明:参数值应当满足正则匹配规则pattern +``` + +## 二、校验方法 + +1. `Check`方法用于单条数据校验,比较简单; +2. `CheckMap`方法用于多条数据校验,校验的主体变量为`map`类型; +3. `CheckStruct`方法用于多条数据校验,校验的主体变量为结构体对象类型; +4. `Check*`方法只有在返回`nil`的情况下,表示数据校验成功,否则返回校验出错的错误信息对象指针`*Error`; + +## 三、校验结果 + +校验结果为一个`Error`对象指针。以下为对象方法: + +1. `FirstItem` 在有多个键名/属性校验错误的时候,用以获取出错的第一个键名,以及其对应的出错规则和错误信息;其顺序性只有使用顺序校验规则时有效,否则返回的结果是随机的; +2. `FirstRule` 会返回`FirstItem`中得第一条出错的规则及错误信息; +3. `FirstString` 会返回`FirstRule`中得第一条规则错误信息; +4. `Map` 会返回`FirstItem`中得出错自规则及对应错误信息`map`; +5. `Maps` 会返回所有的出错键名及对应的出错规则及对应的错误信息(`map[string]map[string]string`); +6. `String` 会返回所有的错误信息,构成一条字符串返回,多个规则错误信息之间以`;`符号连接; +7. `Strings` 会返回所有的错误信息,构成`[]string`类型返回; + +## 四、示例 + +### valid_test.go + +```go +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/util/gvalid" + "testing" +) + +// 单条校验 +func TestCheck(t *testing.T) { + rule := "length:6,16" + if m := gvalid.Check("12345", rule, nil); m != nil { + t.Log(m) + } else { + t.Log("check ok!") + } +} + +// map校验 +func TestCheckMap(t *testing.T) { + params := map[string]interface{}{ + "passport": "john", + "password": "123456", + "password2": "1234567", + } + rules := map[string]string{ + "passport": "required|length:6,16", + "password": "required|length:6,16|same:password2", + "password2": "required|length:6,16", + } + msgs := map[string]interface{}{ + "passport": "账号不能为空|账号长度应当在:min到:max之间", + "password": map[string]string{ + "required": "密码不能为空", + "same": "两次密码输入不相等", + }, + } + if e := gvalid.CheckMap(params, rules, msgs); e != nil { + g.Dump(e.Map()) + g.Dump(e.Maps()) + } else { + t.Log("check ok!") + } +} + +// 对象校验 +func TestCheckStruct(t *testing.T) { + type User struct { + Uid int `gvalid:"uid @integer|min:1#用户UID不能为空"` + Name string `gvalid:"name @required|length:6,30#请输入用户名称|用户名称长度非法"` + } + + user := &User{ + Name: "john", + } + + // 使用结构体定义的校验规则和错误提示进行校验 + g.Dump(gvalid.CheckStruct(user, nil).Map()) + +} + +``` diff --git "a/doc_login/16.GoFrame\347\231\273\345\275\225\345\256\236\346\210\230\344\271\213\347\231\273\345\275\225\345\256\211\345\205\250.md" "b/doc_login/16.GoFrame\347\231\273\345\275\225\345\256\236\346\210\230\344\271\213\347\231\273\345\275\225\345\256\211\345\205\250.md" new file mode 100644 index 0000000..86babbf --- /dev/null +++ "b/doc_login/16.GoFrame\347\231\273\345\275\225\345\256\236\346\210\230\344\271\213\347\231\273\345\275\225\345\256\211\345\205\250.md" @@ -0,0 +1,387 @@ +# GoFrame登录实战之登录安全 + +从整体上看,HTTP就是一个通用的单纯协议机制。因此它具备较多优势,但是在安全性方面则呈劣势。 + +HTTP的不足 + +●通信使用明文(不加密),内容可能会被窃听 + +●不验证通信方的身份,因此有可能遭遇伪装 + +●无法证明报文的完整性,所以有可能已遭篡改 + +![image-20200419001945127](16.secure.assets/image-20200419001945127.png) + +## 一、在浏览器端HTTP是可以随意修改的 + +在Web应用中,从浏览器那接收到的HTTP请求的全部内容,都可以在客户端自由地变更、篡改。所以Web应用可能会接收到与预期数据不相同的内容。 + +客户端校验只是为了用户体验,要保证安全性就一定要做服务端校验; + +## 二、避免传输拦截 + +传输参数进行加密:前端密码进行MD5不可逆加密; + +传输使用https协议。 + +### 三、数据库泄露 + +安全存储用户密码的原则是:如果网站数据泄露了,密码也不能被还原。 + +简单的方式是通过md5 多层加密及加盐。比如: + +```bash +md5( md5( password + salt )[8:20] ) +``` + +服务端数据库存储密码加密bcrypt + +## 四、防止暴力破解 + +1. 验证码防止暴力破解; +2. 为用户体验,可多次相同ip或帐号错误,再进行验证码验证; +3. 多次同一帐号错误,进行一段时间的帐号锁定。 + +## 五、常用Web的攻击方式 + +跨站脚本攻击(Cross-Site Scripting,XSS) + +SQL注入攻击(SQL Injection) + +系统命令注入攻击(OS Command Injection) + +DoS攻击(Denial of Service attack) + +## 六、示例 + +### 目录 + +```bash +D:. +│ bcrypt_test.go +│ go.mod +│ go.sum +│ main.go +│ +├─config +│ config.toml +│ server.crt +│ server.key +│ +├─public +│ md5.js +│ +├─sql +│ init.sql +│ +├─template +│ index.html +│ user_index.html +│ +└─test + test.http +``` + +### config.toml + +```toml +# session存储方式file,memory,redis +SessionStorage = "redis" + +[server] + Address = ":80" + ServerRoot = "public" + SessionIdName = "gSessionId" + SessionPath = "./gession" + SessionMaxAge = "1m" + DumpRouterMap = true + # 系统访问日志 + AccessLogEnabled = true + # 系统异常日志panic + ErrorLogEnabled = true + # 系统日志目录,启动,访问,异常 + LogPath = "gflogs" + +[logger] + # 标准日志目录 + path = "logs" + # 日志级别 + level = "all" + +# 模板引擎配置 +[viewer] + Path = "template" + DefaultFile = "index.html" + Delimiters = ["${", "}"] + +# Redis数据库配置 +[redis] + default = "192.168.31.128:6379,0" + +[database] + [database.logger] + Path = "./dblogs" + Level = "all" + Stdout = true + [database.default] + link = "mysql:root:123456@tcp(192.168.31.128:3306)/gf-login" + debug = true +``` + +### init.sql + +```sql +DROP TABLE IF EXISTS `sys_user`; +CREATE TABLE `sys_user` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', + `uuid` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'UUID', + `login_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '登录名/11111', + `password` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码', + `real_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '真实姓名', + `enable` tinyint(1) NULL DEFAULT 1 COMMENT '是否启用//radio/1,启用,2,禁用', + `update_time` varchar(24) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新时间', + `update_id` int(11) NULL DEFAULT 0 COMMENT '更新人', + `create_time` varchar(24) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `create_id` int(11) NULL DEFAULT 0 COMMENT '创建者', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uni_user_username`(`login_name`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 23 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_user +-- ---------------------------- +INSERT INTO `sys_user` VALUES (1, '94091b1fa6ac4a27a06c0b92155aea6a', 'admin', 'e10adc3949ba59abbe56e057f20f883e', '系统管理员', 1, '2019-12-24 12:01:43', 1, '2017-03-19 20:41:25', 1); +INSERT INTO `sys_user` VALUES (2, '84091b1fa6ac4a27a06c0b92155aea6b', 'test', 'e10adc3949ba59abbe56e057f20f883e', '测试用户', 1, '2019-12-24 12:01:43', 1, '2017-03-19 20:41:25', 1); +``` + +### main.go + +```go +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/os/glog" + "github.com/gogf/gf/os/gsession" + "github.com/gogf/gf/util/gconv" + "github.com/gogf/gf/util/gvalid" + "golang.org/x/crypto/bcrypt" +) + +const SessionUser = "SessionUser" + +func main() { + s := g.Server() + + // 设置存储方式 + sessionStorage := g.Config().GetString("SessionStorage") + if sessionStorage == "redis" { + s.SetSessionStorage(gsession.NewStorageRedis(g.Redis())) + s.SetSessionIdName(g.Config().GetString("server.SessionIdName")) + } else if sessionStorage == "memory" { + s.SetSessionStorage(gsession.NewStorageMemory()) + } + + // 常规注册 + group := s.Group("/") + group.GET("/", func(r *ghttp.Request) { + r.Response.WriteTpl("index.html", g.Map{ + "title": "登录页面", + }) + }) + + // 用户对象 + type User struct { + Username string `gvalid:"username @required|length:5,16#请输入用户名称|用户名称长度非法"` + Password string `gvalid:"password @required|length:31,33#请输入密码|密码长度非法"` + } + + group.POST("/login", func(r *ghttp.Request) { + username := r.GetString("username") + password := r.GetString("password") + + // 使用结构体定义的校验规则和错误提示进行校验 + if e := gvalid.CheckStruct(User{username, password}, nil); e != nil { + r.Response.WriteJson(g.Map{ + "code": -1, + "msg": e.Error(), + }) + r.Exit() + } + + record, err := g.DB().Table("sys_user").Where("login_name = ? ", username).One() + // 查询数据库异常 + if err != nil { + glog.Error("查询数据错误", err) + r.Response.WriteJson(g.Map{ + "code": -1, + "msg": "查询失败", + }) + r.Exit() + } + // 帐号信息错误 + if record == nil { + r.Response.WriteJson(g.Map{ + "code": -1, + "msg": "帐号信息错误", + }) + r.Exit() + } + + // 直接存入前端传输的 + successPwd := record["password"].String() + comparePwd := password + + // 加盐密码 + // salt := "123456" + // comparePwd, _ = gmd5.EncryptString(comparePwd + salt) + + // bcrypt验证 + err = bcrypt.CompareHashAndPassword([]byte(successPwd), []byte(comparePwd)) + + //if comparePwd == successPwd { + if err == nil { + // 添加session + r.Session.Set(SessionUser, g.Map{ + "username": username, + "realName": record["real_name"].String(), + }) + r.Response.WriteJson(g.Map{ + "code": 0, + "msg": "登录成功", + }) + r.Exit() + } + + r.Response.WriteJson(g.Map{ + "code": -1, + "msg": "登录失败", + }) + }) + + // 用户组 + userGroup := s.Group("/user") + userGroup.Middleware(MiddlewareAuth) + // 列表页面 + userGroup.GET("/index", func(r *ghttp.Request) { + realName := gconv.String(r.Session.GetMap(SessionUser)["realName"]) + r.Response.WriteTpl("user_index.html", g.Map{ + "title": "用户信息列表页面", + "realName": realName, + "dataList": g.List{ + g.Map{ + "date": "2020-04-01", + "name": "朱元璋", + "address": "江苏110号", + }, + g.Map{ + "date": "2020-04-02", + "name": "徐达", + "address": "江苏111号", + }, + g.Map{ + "date": "2020-04-03", + "name": "李善长", + "address": "江苏112号", + }, + }}) + }) + userGroup.POST("/logout", func(r *ghttp.Request) { + // 删除session + r.Session.Remove(SessionUser) + + r.Response.WriteJson(g.Map{ + "code": 0, + "msg": "登出成功", + }) + }) + + // 生成秘钥文件 + // openssl genrsa -out server.key 2048 + // 生成证书文件 + // openssl req -new -x509 -key server.key -out server.crt -days 365 + s.EnableHTTPS("config/server.crt", "config/server.key") + s.SetHTTPSPort(8080) + s.SetPort(8199) + + s.Run() +} + +// 认证中间件 +func MiddlewareAuth(r *ghttp.Request) { + if r.Session.Contains(SessionUser) { + r.Middleware.Next() + } else { + // 获取用错误码 + r.Response.WriteJson(g.Map{ + "code": 403, + "msg": "您访问超时或已登出", + }) + } +} +``` + +### bcrypt_test.go + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/crypto/gmd5" + "golang.org/x/crypto/bcrypt" + "testing" +) + +func TestMd5(t *testing.T) { + md5, _ := gmd5.EncryptString("123456") + fmt.Println(md5) +} + +func TestMd5Salt(t *testing.T) { + md5, _ := gmd5.EncryptString("123456") + fmt.Println(md5) + fmt.Println(gmd5.EncryptString(md5 + "123456")) +} + +func TestBcrypt(t *testing.T) { + passwordOK := "123456" + passwordOK, _ = gmd5.EncryptString(passwordOK) + passwordERR := "12345678" + passwordERR, _ = gmd5.EncryptString(passwordERR) + + hash, err := bcrypt.GenerateFromPassword([]byte(passwordOK), bcrypt.DefaultCost) + if err != nil { + fmt.Println(err) + } + //fmt.Println(hash) + + encodePW := string(hash) // 保存在数据库的密码,虽然每次生成都不同,只需保存一份即可 + fmt.Println("###", encodePW) + hash, err = bcrypt.GenerateFromPassword([]byte(passwordOK), bcrypt.DefaultCost) + if err != nil { + fmt.Println(err) + } + encodePW = string(hash) // 保存在数据库的密码,虽然每次生成都不同,只需保存一份即可 + fmt.Println("###", encodePW) + // 其中:$是分割符,无意义;2a是bcrypt加密版本号;10是cost的值;而后的前22位是salt值; + // 再然后的字符串就是密码的密文了。 + + // 正确密码验证 + err = bcrypt.CompareHashAndPassword([]byte(encodePW), []byte(passwordOK)) + if err != nil { + fmt.Println("pw wrong") + } else { + fmt.Println("pw ok") + } + + // 错误密码验证 + err = bcrypt.CompareHashAndPassword([]byte(encodePW), []byte(passwordERR)) + if err != nil { + fmt.Println("pw wrong") + } else { + fmt.Println("pw ok") + } +} +``` diff --git a/doc_login/16.secure.assets/image-20200419001945127.png b/doc_login/16.secure.assets/image-20200419001945127.png new file mode 100644 index 0000000..aa62c35 Binary files /dev/null and b/doc_login/16.secure.assets/image-20200419001945127.png differ diff --git a/doc_login/ReadMe.md b/doc_login/ReadMe.md new file mode 100644 index 0000000..f7066a6 --- /dev/null +++ b/doc_login/ReadMe.md @@ -0,0 +1,11 @@ +# GoFrame实战-登录 + +## 教程目录 + +* [11.GoFrame登录实战之模板引擎.md](11.GoFrame登录实战之模板引擎.md) +* [12.GoFrame登录实战之登录流程.md](12.GoFrame登录实战之登录流程.md) +* [13.GoFrame登录实战之cookie和session](13.GoFrame登录实战之cookie和session.md) +* [14.GoFrame登录实战之session实现](14.GoFrame登录实战之session实现.md) +* [15.GoFrame登录实战之数据校验](15.GoFrame登录实战之数据校验.md) +* [16.GoFrame登录实战之登录安全](16.GoFrame登录实战之登录安全.md) + diff --git "a/doc_regex/21.gregex\347\256\200\344\273\213.md" "b/doc_regex/21.gregex\347\256\200\344\273\213.md" new file mode 100644 index 0000000..2459df7 --- /dev/null +++ "b/doc_regex/21.gregex\347\256\200\344\273\213.md" @@ -0,0 +1,161 @@ +# GoFrame实战之正则表达式介绍 + +## 正则表达式 + +正则表达式(Regular Expression)是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为"元字符")。 + +正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。 + +正则表达式是烦琐的,但它是强大的,学会之后的应用会让你除了提高效率外,会给你带来绝对的成就感。 + +## 发展历史 + +正则表达式的"祖先"可以一直上溯至对人类神经系统如何工作的早期研究。Warren McCulloch 和 Walter Pitts 这两位神经生理学家研究出一种数学方式来描述这些神经网络。 + +1956 年, 一位叫 Stephen Kleene 的数学家在 McCulloch 和 Pitts 早期工作的基础上,发表了一篇标题为"神经网事件的表示法"的论文,引入了正则表达式的概念。正则表达式就是用来描述他称为"正则集的代数"的表达式,因此采用"正则表达式"这个术语。 + +随后,发现可以将这一工作应用于使用 Ken Thompson 的计算搜索算法的一些早期研究,Ken Thompson 是 Unix 的主要发明人。正则表达式的第一个实用应用程序就是 Unix 中的 qed 编辑器。 + +如他们所说,剩下的就是众所周知的历史了。从那时起直至现在正则表达式都是基于文本的编辑器和搜索工具中的一个重要部分。 + + + +## 正则示例 + +### 示例一-文本处理 + +我们要从日志中获取到格式化的【名称,身份证,手机号】,文件test.txt: + +```bash +[2020-08-19 17:33:19.467][INFO][com.XXX1.Utils:83][Utils is call {name=王翦,idcard=110111111111,phone=15311111111}] +[2020-08-19 17:34:19.467][INFO][com.XXX2.Utils:83][Utils is call {name=李牧,idcard=110111111112,phone=15311111112}] +[2020-08-19 17:35:19.467][INFO][com.XXX3.Utils:83][Utils is call {name=廉颇,idcard=110111111113,phone=15311111113}] +[2020-08-19 17:36:19.467][INFO][com.XXX4.Utils:83][Utils is call {name=白起,idcard=110111111114,phone=15311111114}] +``` + +1. 将`\[2020.*Utils is call \{name=`替换为空,然后将`idcard=`、`phone=`和`}]`替换为空 +2. 将`\[2020.*Utils is call \{name=(.*),idcard=(.*),phone=(.*)\}\]`替换为`\1,\2,\3` + +输出结果: + +```bash +王翦,110111111111,15311111111 +李牧,110111111112,15311111112 +廉颇,110111111113,15311111113 +白起,110111111114,15311111114 +``` + +### 示例二-文本处理 + +```bash +[2020-08-19 17:33:19.467][INFO][com.XXX1.Utils:83][Utils is call wangjian@sina.com 123] +[2020-08-19 17:34:19.467][INFO][com.XXX1.Utils:83][Utils is call limu@qq.com 1112] +[2020-08-19 17:35:19.467][INFO][com.XXX1.Utils:83][Utils is call lipo@hotmail.com 1345] +[2020-08-19 17:36:19.467][INFO][com.XXX1.Utils:83][Utils is call baiqi@163.com 123123] +[2020-08-19 17:36:19.467][INFO][com.XXX1.Utils:83][Utils is call qishihuang@163.com.cn 123123] +``` + +1. 将`\[2020.*Utils is call (.*@.*\..*) .*\]`替换为`\1`; + +输出结果: + +```bash +wangjian@sina.com +limu@qq.com +lipo@hotmail.com +baiqi@163.com +qishihuang@163.com.cn +``` + +### 示例三-GREP + +grep 命令用于查找文件里符合条件的字符串。 + +grep基本命令: + +```bash +# grep '[1234567890]{5,10}' -E test.txt --color +[2020-08-19 17:33:19.467][INFO][com.XXX1.Utils:83][Utils is call {name=王翦,idcard=110111111111,phone=15311111111}] +[2020-08-19 17:34:19.467][INFO][com.XXX2.Utils:83][Utils is call {name=李牧,idcard=110111111112,phone=15311111112}] +[2020-08-19 17:35:19.467][INFO][com.XXX3.Utils:83][Utils is call {name=廉颇,idcard=110111111113,phone=15311111113}] +[2020-08-19 17:36:19.467][INFO][com.XXX4.Utils:83][Utils is call {name=白起,idcard=110111111114,phone=15311111114}] +``` + +### 示例四-AWK + +AWK 是一种处理文本文件的语言,是一个强大的文本分析工具。 + +awk 语法: [-F field-separator] '{pattern + action}' {filenames} + +awk基本命令: + +```bash +# awk '/[0-9]{11}/' test.txt +[2020-08-19 17:33:19.467][INFO][com.XXX1.Utils:83][Utils is call {name=王翦,idcard=110111111111,phone=15311111111}] +[2020-08-19 17:34:19.467][INFO][com.XXX2.Utils:83][Utils is call {name=李牧,idcard=110111111112,phone=15311111112}] +[2020-08-19 17:35:19.467][INFO][com.XXX3.Utils:83][Utils is call {name=廉颇,idcard=110111111113,phone=15311111113}] +[2020-08-19 17:36:19.467][INFO][com.XXX4.Utils:83][Utils is call {name=白起,idcard=110111111114,phone=15311111114}] +# awk '/[1234567890]{10}4/' test.txt +[2020-08-19 17:36:19.467][INFO][com.XXX4.Utils:83][Utils is call {name=白起,idcard=110111111114,phone=15311111114}] +``` + +awk分割功能: + +```bash +[root@node177 ~]# awk -F '[,{}]' '/[0-9]{11}/{print $2"\t"$3"\t"$4}' test.txt +name=王翦 idcard=110111111111 phone=15311111111 +name=李牧 idcard=110111111112 phone=15311111112 +name=廉颇 idcard=110111111113 phone=15311111113 +name=白起 idcard=110111111114 phone=15311111114 +``` + +### 示例四-SED + +**sed**是一种流编辑器,它是文本处理中非常中的工具,能够完美的配合正则表达式使用,功能不同凡响。 + +sed命令:sed [options] 'command' file(s) + +sed基本命令 + +```bash +# sed '/[1234567890]{11}/p' test.txt +[2020-08-19 17:33:19.467][INFO][com.XXX1.Utils:83][Utils is call {name=王翦,idcard=110111111111,phone=15311111111}] +[2020-08-19 17:34:19.467][INFO][com.XXX2.Utils:83][Utils is call {name=李牧,idcard=110111111112,phone=15311111112}] +[2020-08-19 17:35:19.467][INFO][com.XXX3.Utils:83][Utils is call {name=廉颇,idcard=110111111113,phone=15311111113}] +[2020-08-19 17:36:19.467][INFO][com.XXX4.Utils:83][Utils is call {name=白起,idcard=110111111114,phone=15311111114}] +``` + +sed替换,sed 's/要被取代的字串/新的字串/g': + +```bash +[root@node177 ~]# sed 's/[0-9]\{11\}/c/g' test2.txt +[2020-08-19 17:33:19.467][INFO][com.XXX1.Utils:83][Utils is call {name=王翦,idcard=c1,phone=c}] +[2020-08-19 17:34:19.467][INFO][com.XXX2.Utils:83][Utils is call {name=李牧,idcard=c2,phone=c}] +[2020-08-19 17:35:19.467][INFO][com.XXX3.Utils:83][Utils is call {name=廉颇,idcard=c3,phone=c}] +[2020-08-19 17:36:19.467][INFO][com.XXX4.Utils:83][Utils is call {name=白起,idcard=c4,phone=c}] +``` + +### 示例五-GoFrame + +`gregex`提供了对正则表达式的支持,底层是对标准库`regexp`的封装,极大地简化了正则的使用,并采用了解析缓存设计,提高了执行效率。 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/text/gregex" +) + +func main() { + match, _ := gregex.MatchString(`(\w+).+\-\-\s*(.+)`, `GF is best! -- John`) + fmt.Printf(`%s says "%s" is the one he loves!`, match[2], match[1]) +} +``` + +执行后,输出结果为: + +```bash +John says "GF" is the one he loves! +``` + diff --git "a/doc_regex/22.gregex\346\255\243\345\210\231\350\257\246\350\247\243.md" "b/doc_regex/22.gregex\346\255\243\345\210\231\350\257\246\350\247\243.md" new file mode 100644 index 0000000..b59f304 --- /dev/null +++ "b/doc_regex/22.gregex\346\255\243\345\210\231\350\257\246\350\247\243.md" @@ -0,0 +1,343 @@ +# GoFrame实战之正则表达式语法详解 + +## 1. 正则介绍 + +### 1.1. 元字符 + +#### 示例一 文本日期格式 + +```bash +[2020-08-19 17:36:19.467][INFO][com.XXX4.Utils:83][Utils is call {name=白起,idcard=110111111114,phone=15311111114}] +2019-07-20 +1999-05-05 +2019-1-1 +``` + +匹配日期规则: + +正则1:`\d{4}-\d{2}-\d{2}` + +正则2:`[21]\d{3}-\d{1,2}-\d{1,2}` + +正则3:`[21]\d{3}-[01]?\d-[0123]?\d` + +#### 示例二 换行和所有字符匹配 + +```bash +123 abc +ABC +``` + +匹配全内容和换行符 + +正则1:`\d+\s\w+\r\nABC` +正则2:`.*` +正则3:`[\w|\W]*` + +#### 示例三 字符转义 + +```bash +123 abc +ABCW\*() +``` + +示例1:`\d+\s\w+\r\nABCW\\\*\(\)` + +#### 示例四 特殊字符 + +```bash +123 abc libang abc libang +``` + +正则1:`\d+\s(\w+)\s\w+\s\w+\s\w+` + +正则2:`\d+\s((\w+)\s)+\w+` + +正则3:`\d+(\s[abc]+\s\w+)+` + +正则4:`\d+\s((\w+)\s){1,3}\w+` + +正则5:`^\d+\s((\w+)\s){1,3}\w+$` + +正则6:`\babc\b` + +正则7:`(\sabc\slibang)+?` + +正则8:`(\sabc\slibang)+` + +正则9:`(\sabc\slibang)+|123` + +#### 示例五 懒惰和贪婪模式 + +```bash +aabab +``` + +正则1:`a.*?b` + +正则2:`a.*b` + +正则3:`a.+?b` + +正则4:`a.??b` + +#### 示例六 邮箱格式 + +```bash +22222zhuchongba@channelsoft.com11111zhuchongba +``` + +正则1(第一组):`(\w+)@(?:\w+)\.(\w+)` + +正则2(使用第一组):`(\w+)@(\w+)\.(\w+)\1` + +正则3(减少分组):`(\w+)@(?:\w+)\.(\w+)` + +正则4(获取前面):`(\w+)@(\w+)\.(\w+)(?=11111)` + +正则5(获取后面):`(?<=22222)(\w+)@(\w+)\.(\w+)` + +正则6:`^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$` + +## 2. 常用规则 + +### 2.1. 字符集 + +| **字符集合** | **说明** | +| ------------ | ------------------------------------------------------- | +| **.** | 小数点可以匹配除了换行符(\n)以外的任意一个字符 | +| **\w** | 可以匹配任何一个字母或者数字或者下划线 | +| **\W** | W大写,可以匹配任何一个字母或者数字或者下划线以外的字符 | +| **\s** | 可以匹配空格、制表符、换页符等空白字符的其中任意一个 | +| **\S** | S大写,可以匹配任何一个空白字符以外的字符 | +| **\d** | 可以匹配任何一个 0~9 数字字符 | +| **\D** | D大写,可以匹配任何一个非数字字符 | + +### 2.2. 转义符 + +| **转义符** | **说明** | +| -------------- | ------------------------------------------------ | +| **\a** | 响铃符 = **\x07** | +| **\f** | 换页符 = **\x0C** | +| **\n** | 换行符 = **\x0A** | +| **\r** | 回车符 = **\x0D** | +| **\t** | 制表符 = **\x09** | +| **\v** | 垂直制表符 = **\x0B** | +| **\e** | ESC 符 = **\x1B** | +| **\b** | 单词的开头或结尾,也就是单词的分界处 | +| **\x20** | 使用两位十六进制表示形式,可与该编号的字符匹配 | +| **\u002B** | 使用四位十六进制表示形式,可与该编号的字符匹配 | +| **\x{20A060}** | 使用任意位十六进制表示形式,可与该编号的字符匹配 | + +### 2.3. 特殊字符 + +| **字符** | **说明** | +| -------- | ------------------------------------------------------------ | +| **^** | 匹配输入字符串的开始位置。要匹配 "^" 字符本身,请使用 "\^" | +| **$** | 匹配输入字符串的结尾位置。要匹配 "$" 字符本身,请使用 "\$" | +| **( )** | 标记一个子表达式的开始和结束位置。要匹配小括号,请使用 "\(" 和 "\)" | +| **[ ]** | 用来自定义能够匹配 '多种字符' 的表达式。要匹配中括号,请使用 "\[" 和 "\]" | +| **{ }** | 修饰匹配次数的符号。要匹配大括号,请使用 "\{" 和 "\}" | +| **.** | 匹配除了换行符(\n)以外的任意一个字符。要匹配小数点本身,请使用 "\." | +| **?** | 修饰匹配次数为 0 次或 1 次。要匹配 "?" 字符本身,请使用 "\?" | +| **+** | 修饰匹配次数为至少 1 次。要匹配 "+" 字符本身,请使用 "\+" | +| ***** | 修饰匹配次数为 0 次或任意次。要匹配 "*" 字符本身,请使用 "\*" | +| **\|** | 左右两边表达式之间 "或" 关系。匹配 "\|" 本身,请使用 "\|" | + +### 2.4. 限定符 + +| **限定符** | **说明** | +| ---------- | ------------------------------------------------------------ | +| **{n}** | 表达式固定重复n次,比如:"**\w****{2}**" 相当于 "**\w\w**" | +| **{m, n}** | 表达式尽可能重复n次,至少重复m次:"**ba****{1,3}**"可以匹配 "ba"或"baa"或"baaa" | +| **{m, }** | 表达式尽可能的多匹配,至少重复m次:"**\w\d****{2,}**"可以匹配 "a12","x456"... | +| **?** | 表达式尽可能匹配1次,也可以不匹配,相当于 **{0, 1}** | +| **+** | 表达式尽可能的多匹配,至少匹配1次,相当于 **{1, }** | +| ***** | 表达式尽可能的多匹配,最少可以不匹配,相当于 **{0, }** | + +### 2.5. 懒惰模式 + +| **限定符** | **说明** | +| ----------- | -------------------------------------------------------- | +| **{m, n}?** | 表达式尽量只匹配m次,最多重复n次。 | +| **{m, }?** | 表达式尽量只匹配m次,最多可以匹配任意次。 | +| **??** | 表达式尽量不匹配,最多匹配1次,相当于 **{0, 1}?** | +| **+?** | 表达式尽量只匹配1次,最多可匹配任意次,相当于 **{1, }?** | +| ***?** | 表达式尽量不匹配,最多可匹配任意次,相当于 **{0, }?** | + +### 2.6. 贪婪模式 + +| **限定符** | **说明** | +| ----------- | -------------------------------------------------------- | +| **{m, n}+** | 表达式尽可能重复n次,至少重复m次。 | +| **{m, }+** | 表达式尽可能的多匹配,至少重复m次。 | +| **?+** | 表达式尽可能匹配1次,也可以不匹配,相当于 **{0, 1}+** | +| **++** | 表达式尽可能的多匹配,至少匹配1次,相当于 **{1, }+** | +| ***+** | 表达式尽可能的多匹配,最少可以不匹配,相当于 **{0, }+** | + +### 2.7. 反向引用 + +| 代码/语法 | **说明** | +| ------------ | ------------------------------------------------------------ | +| (exp) | 匹配exp,并捕获文本到自动命名的组里 | +| (?exp) | 匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp) | +| (?:exp) | 匹配exp,不捕获匹配的文本,也不给此分组分配组号 | +| (?=exp) | 匹配exp前面的位置 | +| (?<=exp) | 匹配exp后面的位置 | +| (?!exp) | 匹配后面跟的不是exp的位置 | +| (?]*>.*?|<.*? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力)* +20. *首尾空白字符的正则表达式:^\s*|\s*$或(^\s*)|(\s*$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式) +21. 腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始) +22. 中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字) +23. IP地址:\d+\.\d+\.\d+\.\d+ (提取IP地址时有用) +24. IP地址:((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)) \ No newline at end of file diff --git "a/doc_regex/23.gregex\344\275\277\347\224\250.md" "b/doc_regex/23.gregex\344\275\277\347\224\250.md" new file mode 100644 index 0000000..558b987 --- /dev/null +++ "b/doc_regex/23.gregex\344\275\277\347\224\250.md" @@ -0,0 +1,109 @@ +# GoFrame实战之正则表达式使用 + +本章节主要讲解GoFrame中的正则表达式使用 + +```go +package test + +import ( + "fmt" + "github.com/gogf/gf/text/gregex" + "testing" +) + +// IsMatch +func TestIsMatch(t *testing.T) { + // 校验时间是否合法 + var pattern = `\d{4}-\d{2}-\d{2}` + s1 := []byte(`2019-07-20`) + fmt.Println("IsMatch1", gregex.IsMatch(pattern, s1)) + pattern = `[21]\d{3}-\d{1,2}-\d{1,2}` + fmt.Println("IsMatch2", gregex.IsMatch(pattern, s1)) +} + +// IsMatchString +func TestIsMatchString(t *testing.T) { + var pattern = `[21]\d{3}-[01]?\d-[0123]?\d` + s1 := `2019-07-20` + fmt.Println("IsMatchString", gregex.IsMatchString(pattern, s1)) +} + +var ( + textStr = "123 xiangyu liubang xiangyu liubang" + patternStr = `\d+\s(\w+)\s\w+\s\w+\s\w+` + patternStr2 = `\d+\s(\w+)` + patternStr3 = `(\w+)\sliubang` +) + +// Match +func TestMatch(t *testing.T) { + subs, err := gregex.Match(patternStr, []byte(textStr)) + if err != nil { + t.Error("Match", err) + } + fmt.Println("Match", string(subs[0]), "##group:", string(subs[1]), err) +} + +// MatchString +func TestMatchString(t *testing.T) { + // 匹配全部内容 + subs, err := gregex.MatchString(patternStr, textStr) + if err != nil { + t.Error("MatchString", err) + } + fmt.Println("MatchString", subs[0], "##group:", subs[1], err) + + + // 匹配部分内容 + subs, err = gregex.MatchString(patternStr2, textStr) + if err != nil { + t.Error("MatchString2", err) + } + fmt.Println("MatchString2", subs[0], "##group:", subs[1], err) +} + +// MatchAll +func TestMatchAll(t *testing.T) { + allGroup, err := gregex.MatchAll(patternStr3, []byte(textStr)) + if err != nil { + t.Error("MatchAll", err) + } + fmt.Println("MatchAll", string(allGroup[0][0]), "##group:", string(allGroup[0][1]), err) + fmt.Println("MatchAll", string(allGroup[1][0]), "##group:", string(allGroup[1][1]), err) +} + +// MatchAllString +func TestMatchAllString(t *testing.T) { + allGroup, err := gregex.MatchAllString(patternStr3, textStr) + if err != nil { + t.Error("MatchAllString", err) + } + fmt.Println("MatchAllString", allGroup, "##group:", allGroup[0][1], err) +} + +// Replace +func TestReplace(t *testing.T) { + replace, err := gregex.Replace(patternStr3, []byte("zhuyuanzhang chenyouliang"),[]byte(textStr)) + if err != nil { + t.Error("Replace", err) + } + fmt.Println("Replace", string(replace), "##src:", textStr, err) + +} + +// ReplaceString +func TestReplaceString(t *testing.T) { + replacedStr, err := gregex.ReplaceString(patternStr3, "zhuyuanzhang chenyouliang",textStr) + if err != nil { + t.Error("ReplaceString", err) + } + fmt.Println("ReplaceString", replacedStr, "##src:", textStr, err) +} + +// Split +func TestSplit(t *testing.T) { + items := gregex.Split(`\sxiangyu\s`, textStr) + fmt.Println("Split", items,"###0:",items[0], "##src:", textStr) +} +``` + diff --git a/doc_regex/ReadMe.md b/doc_regex/ReadMe.md new file mode 100644 index 0000000..c44396b --- /dev/null +++ b/doc_regex/ReadMe.md @@ -0,0 +1,8 @@ +# GoFrame实战-正则表达式 + +## 教程目录 + +* [21.gregex简介.md](21.gregex简介.md) +* [22.gregex正则详解.md](22.gregex正则详解.md) +* [23.gregex使用.md](23.gregex使用.md) +