连续复制
一键复制
一键打包

02.hello

web

main.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(8080)
    s.Run()
}

hello

hello.go
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"))
}

03.web

config

config.toml
[server]
    # 端口号
    Address          = ":8199"
    # 静态目录
    ServerRoot       = "public"
    # 入口文件
    IndexFiles       = ["index.html", "main.html"]
    # 系统访问日志
    AccessLogEnabled = true
    # 系统异常日志panic
    ErrorLogEnabled  = true
    # 系统日志目录,启动,访问,异常
    LogPath          = "gflogs"

[logger]
    # 标准日志目录
    path   = "logs"
    # 日志级别
    level  = "all"

public

index.html
welcome static html
hello.html
welcome hello

main.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()
}

04.router

test.http

### 常规注册
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

###

main.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")
}

05.client

test

client_test.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())
    }
}

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("/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()
}

06.config

configTest

config2.toml
config2 = "111"
config1.toml
study = "hello study"
study1 = "hello study1"

config

config.toml
# 模板引擎目录
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"

config_test.go

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"))
}

main.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()

}

07.log

config

config.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

main.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("............")

}

08.database

config

config.toml
# 数据库配置
[database]
    link = "mysql:root:A123456@tcp(127.0.0.1:13306)/test"

db_test.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()
}

09.redis

config

config.toml
# Redis数据库配置
[redis]
    default = "127.0.0.1:6379,0"
    cache   = "127.0.0.1:6379,1,123456?idleTimeout=600"

main.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))
    g.Redis().Do("SET", "miniapp:"+appid+":accessToken", res.AccessToken)
    g.Redis().Do("EXPIRE", "miniapp:"+appid+":accessToken", 7000)

    // 获取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())

}

10.tools

tools_test.go

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)
}

11.template

config

config.toml

# 模板引擎配置
[viewer]
    Path        = "template"
    DefaultFile = "index.html"
    Delimiters  =  ["${", "}"]

template

index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <!-- import CSS -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <style>
        .el-row {
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
<div id="app">
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <span>${ .title }</span>
            <span>${if .show}【展示】${end}</span>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="24">
            <template>
                <el-table
                        :data="tableData"
                        style="width: 100%">
                    <el-table-column
                            prop="date"
                            label="日期"
                            width="180">
                    </el-table-column>
                    <el-table-column
                            prop="name"
                            label="姓名"
                            width="180">
                    </el-table-column>
                    <el-table-column
                            prop="address"
                            label="地址">
                    </el-table-column>
                </el-table>
            </template>
        </el-col>
    </el-row>

    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            ${"我是中国人" | substr 2 -1}
        </el-col>
    </el-row>

    ${include "include.html" .}

</div>
</body>
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>

<script>
    ${/*
     tableData: [{
        date: '2016-05-02',
        name: '王小虎',
        address: '上海市普陀区金沙江路 1518 弄'
    }]
    */}

    var listData = new Array();
    var data;
    ${range $index, $elem := .listData}
    data = {};
    ${range $key, $value := $elem}
    data['${$key}'] = '${$value}'
    ${end}
    listData.push(data)
    ${end}
    var vm = new Vue({
        el: '#app',
        data: {
            visible: false,
            tableData: listData
        }
    })
</script>
</html>
include.html
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <span style="font-weight: bold">这里是通过include引用的文件内容</span>
        </el-col>
    </el-row>

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": "列表页面",
            "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()
}

12.login

config

config.toml
# 账号
username = "admin"
# 密码
password = "123456"

# 模板引擎配置
[viewer]
    Path        = "template"
    DefaultFile = "index.html"
    Delimiters  =  ["${", "}"]

template

index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <!-- import CSS -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <style>
        .el-row {
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
<div id="app">
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <span>${ .title }</span>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="6" :offset="6">
            <el-input v-model="username" placeholder="请输入内容"></el-input>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="6" :offset="6">
            <el-input placeholder="请输入密码" v-model="password" show-password></el-input>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <el-button @click="login">登录</el-button>
        </el-col>
    </el-row>


</div>
</body>
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>

<script>
    new Vue({
        el: '#app',
        data: function () {
            return {
                visible: false,
                username: '',
                password: ''
            }
        },
        methods: {
            login: function () {
                axios.post('/login', {       // 还可以直接把参数拼接在url后边
                    username: this.username,
                    password: this.password
                }).then(function (res) {
                    console.log(res.data)
                    if (res.data.code == 0) {
                        alert(res.data.msg)
                        window.location.href = "/user/index"
                    } else {
                        alert("失败:" + res.data.msg)
                    }
                }).catch(function (error) {
                    console.log(error);
                });
            }
        }
    })
</script>
</html>
user_index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <!-- import CSS -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <style>
        .el-row {
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
<div id="app">
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <span>${ .title }</span>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="24">
            <template>
                <el-table
                        :data="tableData"
                        style="width: 100%">
                    <el-table-column
                            prop="date"
                            label="日期"
                            width="180">
                    </el-table-column>
                    <el-table-column
                            prop="name"
                            label="姓名"
                            width="180">
                    </el-table-column>
                    <el-table-column
                            prop="address"
                            label="地址">
                    </el-table-column>
                </el-table>
            </template>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <el-button @click="logout">登出</el-button>
        </el-col>
    </el-row>


</div>
</body>
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>

<script>
    ${/*
    tableData: [{
        date: '2016-05-02',
        name: '王小虎',
        address: '上海市普陀区金沙江路 1518 弄'
    }]
    */}

    var listData = new Array();
    var data;
    ${range $index, $elem := .dataList}
        data = {};
        ${range $key, $value := $elem}
        data['${$key}'] = '${$value}'
        ${end}
        listData.push(data)
    ${end}

    var vm = new Vue({
        el: '#app',
        data: {
            visible: false,
            tableData: listData
        },
        methods: {
            logout: function () {
                axios.post('/logout', {}).then(function (res) {
                    console.log(res.data)
                    if (res.data.code == 0) {
                        alert(res.data.msg)
                        window.location.href = "/"
                    } else {
                        alert("失败:" + res.data.msg)
                    }
                }).catch(function (error) {
                    console.log(error);
                });
            }
        },
        mounted: function () {

        }
    })
</script>
</html>

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()
}

14.gsession

test

test.http
GET http://127.0.0.1/user/index
Cookie: gSessionId=C24J0ONC99ECJPEWBE


###

config

config.toml
# 账号
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"

template

index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <!-- import CSS -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <style>
        .el-row {
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
<div id="app">
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <span>${ .title }</span>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="6" :offset="6">
            <el-input v-model="username" placeholder="请输入内容"></el-input>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="6" :offset="6">
            <el-input placeholder="请输入密码" v-model="password" show-password></el-input>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <el-button @click="login">登录</el-button>
        </el-col>
    </el-row>


</div>
</body>
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>

<script>
    new Vue({
        el: '#app',
        data: function () {
            return {
                visible: false,
                username: '',
                password: ''
            }
        },
        methods: {
            login: function () {
                axios.post('/login', {       // 还可以直接把参数拼接在url后边
                    username: this.username,
                    password: this.password
                }).then(function (res) {
                    console.log(res.data)
                    if (res.data.code == 0) {
                        alert(res.data.msg)
                        window.location.href = "/user/index"
                    } else {
                        alert("失败:" + res.data.msg)
                    }
                }).catch(function (error) {
                    console.log(error);
                });
            }
        }
    })
</script>
</html>
user_index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <!-- import CSS -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <style>
        .el-row {
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
<div id="app">
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <span>${ .title }</span>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="24">
            <template>
                <el-table
                        :data="tableData"
                        style="width: 100%">
                    <el-table-column
                            prop="date"
                            label="日期"
                            width="180">
                    </el-table-column>
                    <el-table-column
                            prop="name"
                            label="姓名"
                            width="180">
                    </el-table-column>
                    <el-table-column
                            prop="address"
                            label="地址">
                    </el-table-column>
                </el-table>
            </template>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <el-button @click="logout">登出</el-button>
        </el-col>
    </el-row>


</div>
</body>
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>

<script>
    ${/*
    tableData: [{
        date: '2016-05-02',
        name: '王小虎',
        address: '上海市普陀区金沙江路 1518 弄'
    }]
    */}

    var listData = new Array();
    var data;
    ${range $index, $elem := .dataList}
    data = {};
    ${range $key, $value := $elem}
    data['${$key}'] = '${$value}'
    ${end}
    listData.push(data)
    ${end}

    var vm = new Vue({
        el: '#app',
        data: {
            visible: false,
            tableData: listData
        },
        methods: {
            logout: function () {
                axios.post('/user/logout', {}).then(function (res) {
                    console.log(res.data)
                    if (res.data.code == 0) {
                        alert(res.data.msg)
                        window.location.href = "/"
                    } else {
                        alert("失败:" + res.data.msg)
                    }
                }).catch(function (error) {
                    console.log(error);
                });
            }
        },
        mounted: function () {

        }
    })
</script>
</html>

main.go

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":  "您访问超时或已登出",
        })
    }
}

15.gvalid

valid_test.go

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())

}

16.secure

bcrypt_test.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")
    }
}

test

test.http
POST http://127.0.0.1/user/index
Cookie: gSessionId=C1YKVY1S9PUKMPBQQB

###

GET https://127.0.0.1/user/index

###

config

server.key
-----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-----
server.crt
-----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-----
config.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

template

index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <!-- import CSS -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <style>
        .el-row {
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
<div id="app">
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <span>${ .title }</span>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="6" :offset="6">
            <el-input v-model="username" placeholder="请输入内容"></el-input>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="6" :offset="6">
            <el-input placeholder="请输入密码" v-model="password" show-password></el-input>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <el-button @click="login">登录</el-button>
        </el-col>
    </el-row>


</div>
</body>
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>

<script src="/md5.js"></script>

<script>
    new Vue({
        el: '#app',
        data: function () {
            return {
                visible: false,
                username: '',
                password: ''
            }
        },
        methods: {
            login: function () {
                var md5Password = hex_md5(this.password);
                axios.post('/login', {       // 还可以直接把参数拼接在url后边
                    username: this.username,
                    password: md5Password
                }).then(function (res) {
                    console.log(res.data)
                    if (res.data.code == 0) {
                        alert(res.data.msg)
                        window.location.href = "/user/index"
                    } else {
                        alert("失败:" + res.data.msg)
                    }
                }).catch(function (error) {
                    console.log(error);
                });
            }
        }
    })
</script>
</html>
user_index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <!-- import CSS -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <style>
        .el-row {
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
<div id="app">
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <span>欢迎【${.realName }】访问 ${ .title }</span>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="24">
            <template>
                <el-table
                        :data="tableData"
                        style="width: 100%">
                    <el-table-column
                            prop="date"
                            label="日期"
                            width="180">
                    </el-table-column>
                    <el-table-column
                            prop="name"
                            label="姓名"
                            width="180">
                    </el-table-column>
                    <el-table-column
                            prop="address"
                            label="地址">
                    </el-table-column>
                </el-table>
            </template>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <el-button @click="logout">登出</el-button>
        </el-col>
    </el-row>


</div>
</body>
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>

<script>
    ${/*
    tableData: [{
        date: '2016-05-02',
        name: '王小虎',
        address: '上海市普陀区金沙江路 1518 弄'
    }]
    */}

    var listData = new Array();
    var data;
    ${range $index, $elem := .dataList}
    data = {};
    ${range $key, $value := $elem}
    data['${$key}'] = '${$value}'
    ${end}
    listData.push(data)
    ${end}

    var vm = new Vue({
        el: '#app',
        data: {
            visible: false,
            tableData: listData
        },
        methods: {
            logout: function () {
                axios.post('/user/logout', {}).then(function (res) {
                    console.log(res.data)
                    if (res.data.code == 0) {
                        alert(res.data.msg)
                        window.location.href = "/"
                    } else {
                        alert("失败:" + res.data.msg)
                    }
                }).catch(function (error) {
                    console.log(error);
                });
            }
        },
        mounted: function () {

        }
    })
</script>
</html>

public

md5.js
/*
 * 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;
}

main.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":  "您访问超时或已登出",
        })
    }
}

sql

init.sql
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;

17.gfcli

docker

.gitkeep

app

model
.gitkeep
user
user_model.go
// ==========================================================================
// 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 <where> 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 <limit> 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 <page> 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 <duration> < 0, which means it clear the cache with given <name>.
// If the parameter <duration> = 0, which means it never expires.
// If the parameter <duration> > 0, which means it expires after <duration>.
//
// The optional parameter <name> is used to bind a name to the cache, which means you can later
// control the cache like changing the <duration> or clearing the cache with specified <name>.
//
// 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 <data> 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 <where> 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 <where> 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()}
}
user.go
// ============================================================================
// This is auto-generated by gf cli tool only once. Fill this file as you wish.
// ============================================================================

package user

// Fill with you ideas below.
user_entity.go
// ==========================================================================
// 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()
}
api
hello
hello.go
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)
}
service
.gitkeep
user
userSvc.go
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
}

boot

.gitkeep
boot.go
package boot

func init() {

}
data.go
package boot

import "github.com/gogf/gf/os/gres"

func init() {
    if err := gres.Add("1f8b08000000000002ffa495df4fd35014c70f63ccad842063f00292a6f8a089740c1065890924047fc0c394f9b48ca46c9752b85b477b0b1aa3893e1a9ff0cdf8c203897f80f1c5575f4de41ff15fd0dc765d7bb77bc7d6358176f79e7ecef79c9cfb6d617b389e8124004c9f7d2940e81a871454ccfa81a16755dd20c708355e16476068e1e272afb07d23198e0d281f3fb0947440f16e2a316bd805fdfa7eb9372f3f29160bf22eb24e9125956cf75e96521bd5aa856c5ba6d72359c93fccadad2952ca8b7b619ac45d6e38fbd8a828526ac7d40b1a39f4c3b1a9db0bfa812249f3f28ea9ebc852a512761fca128d690507f18abbb1834e110e36348cbdf55d52351de2af13cb41945d44b506d60852a5d2a981ce0474d28cf2489be8407330d93230a29b46bd8a5ea987a486fd6d6cd40c822c9bbe2b9794db6f947bb2f25629d3849b1ad1f6359b26ac361fbd94d8a81fbbc96aafed139cb74c93e4734bcb2bf757d749a57127b7f4405d5417d55c3eb7bcbcb87a374b904dbc8455b4efe87e4d7421c822e366efe8722ba11a6e64a85cbfeff649b394503fc3ad0cb5d3cb4a8729fefcfc713c0640ff82617afae92b334c89d630b90334637ceb3289edf33c0a29f006264bdbddc32cb713322ca19f63d18e1a0b500dece8463d0263ba9d3188a0f1006621db74ac0a8a4099eca4642bb61d8134cb270d52e2140769d4343d4aa17322d62002d31ce85194e6cd7041fd48e39d3c8f18e1e44d400a7c131c444432c4e92263289619160fb97ffd784fff8b3f7001857ed2c2fe9466285b0185f7810b83784df53b04eb9f63d0696ee25a465b2fdf847f1b7f622030373121c3d4911e86ebcc4d8c1a63c494035487b98919d38c9c8b76467f82c619417f0318c7dcc4944986b212876bcc4d4c9a658a7bc727f557e21423ee8a836c3737316b8e91776b047a37373134cd08b438d0a3de9a37c3a8fbc9057597d6fde48d25a0d3dcc472261839cf12d0d5dcc422928c88f31027246324e1054cc0ef38c095fbeb7f000000ffff107950e1b40b0000"); err != nil {
        panic(err)
    }
}

config

.gitkeep
config.toml
# 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

Dockerfile

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


template

.gitkeep

document

.gitkeep

README.MD

MD

GoFrame Project

https://goframe.org

public

plugin
.gitkeep
html
.gitkeep
resource
css
.gitkeep
js
.gitkeep
image
.gitkeep

i18n

.gitkeep

main.go

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()
}

router

.gitkeep
router.go
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)
    })
}

23.gregex

test

gregex_test.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)
}

doc_basic

04.goframe路由注册.md

md

GoFrame路由注册

一、路由规则

gf框架自建了非常强大的路由功能,提供了比任何同类框架更加出色的路由特性,支持流行的命名匹配规则、模糊匹配规则及字段匹配规则,并提供了优秀的优先级管理机制。

该方法是路由注册的最基础方法,其中的pattern为路由注册规则字符串,在其他路由注册方法中也会使用到,参数格式如下:

[HTTPMethod:]路由规则[@域名]

其中HTTPMethod(支持的Method:GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE)和@域名为非必需参数,一般来说直接给定路由规则参数即可,BindHandler会自动绑定所有的请求方式,如果给定HTTPMethod,那么路由规则仅会在该请求方式下有效。@域名可以指定生效的域名名称,那么该路由规则仅会在该域名下生效。

BindHandler是最原生的路由注册方法,在大部分场景中,我们通常使用 分组路由 方式来管理路由

示例:

    // hello方法,post调用
    s.BindHandler("POST:/hello", func(r *ghttp.Request) {
        r.Response.Writeln("url" + r.Router.Uri)
    })

二、回调函数注册

回调函数注册方式是最简单且最灵活的的路由注册方式,注册的服务可以是一个实例化对象的方法地址,也可以是一个包方法地址。服务需要的数据可以通过模块内部变量形式或者对象内部变量形式进行管理,开发者可根据实际情况进行灵活控制。

我们可以直接通过BindHandler方法完成回调函数的注册,在框架的开发手册中很多地方都使用了回调函数注册的方式来做演示,因为这种注册方式比较简单。

示例:

    // 方法注册
    s.BindHandler("/total", Total)

三、执行对象注册

执行对象注册是在注册时便给定一个实例化的对象,以后每一个请求都交给该对象(同一对象)处理,该对象常驻内存不释放。服务端进程在启动时便需要初始化这些执行对象,并且这些对象需要自行负责对自身数据的并发安全维护(往往对象的成员变量应当是并发安全的,每个请求执行完毕后该对象不会销毁,其成员变量也不会释放)。

    // 对象注册
    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提供更灵活强大的插件机制。经典的中间件洋葱模型:

经典的中间件洋葱模型

示例:

    // 分组注册及中间件
    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 StringBody参数解析。

  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*方法用于模板解析,解析模板文件或者模板内容,返回解析后的内容。

七、教程示例

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")
}

访问结果:

### 常规注册
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

###

05.goframe的HTTP客户端.md

md

GoFrame的HTTP客户端

一、HTTP协议介绍

超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。

请求:

image-20200316235850132

响应:

image-20200316235911263

放问GF启动的网址,通过Chrome F12查看NetWork中的URL;

image-20200316235457715

优点:简单方便,浏览器支持完善,工具链成熟;

二、GF的HTTP客户端

这个先启动一个gf的http server,然后我们通过go test 来测试ghttp client;

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("/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 参数。

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协议进行学习;

06.goframe配置文件.md

md

GoFrame配置文件

一、配置文件介绍

GF的配置管理由gcfg模块实现,gcfg模块是并发安全的,仅提供配置文件读取功能,不提供数据写入/修改功能,支持的数据文件格式包括: JSONXMLYAML/YMLTOMLINI,项目中开发者可以灵活地选择自己熟悉的配置文件格式来进行配置管理。

默认读取执行文件所在目录及其下的config目录,默认读取的配置文件为config.toml;toml类型文件也是默认的、推荐的配置文件格式,如果想要自定义文件格式,可以通过SetFileName方法修改默认读取的配置文件名称(如:config.json, cfg.yaml, cfg.xml, cfg.ini等等)。

注:TOML大小写敏感,必须是UTF-8编码;

二、自动检测更新

配置管理器使用了缓存机制,当配置文件第一次被读取后会被缓存到内存中,下一次读取时将会直接从缓存中获取,以提高性能。同时,配置管理器提供了对配置文件的自动检测更新机制,当配置文件在外部被修改后,配置管理器能够即时地刷新配置文件的缓存内容。

配置管理器的自动检测更新机制是gf框架特有的一大特色。

三、示例

项目目录

D:.
│  config_test.go -- 测试文件
│  go.mod
│  go.sum
│  main.go -- web自动更新配置演示
│
├─config
│      config.toml -- 标准配置文件
│
└─configTest -- 定制目录和配置文件
        config1.toml  
        config2.toml

config.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

study = "hello study"
study1 = "hello study1"

config2.toml

config2 = "111"

main.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

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

module gf_config

go 1.14

require github.com/gogf/gf v1.11.7

05.goframe的HTTP客户端.assets

07.goframe日志打印.md

md

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支持以下几种日志级别常量设定:

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配置项。

[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

五、示例

项目目录

D:.
│  go.mod
│  go.sum
│  main.go
│
└─config
        config.toml

main.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

[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

04.goframe路由注册.assets

09.goframeRedis操作.md

md

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中的配置示例如下:

# 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-端口
db0数据库
pass-授权密码
maxIdle0允许限制的连接数(0表示不闲置)
maxActive0最大连接数量限制(0表示不限制)
idleTimeout60连接最大空闲时间(单位秒,不允许设置为0)
maxConnLifetime60连接最长存活时间(单位秒,不允许设置为0)

其中的defaultcache分别表示配置分组名称,我们在程序中可以通过该名称获取对应配置的redis对象。不传递分组名称时,默认使用redis.default配置分组项)来获取对应配置的redis客户端单例对象。

三、结果处理

可以看到通过客户端方法Do/Receive获取的数据都是二进制形式[]byte的,需要开发者手动进行数据转换。

当然,gredis模块也提供了DoVar/ReceiveVar方法,用以获取可供方便转换的gvar.Var通用变量结果。

通过gvar.Var的强大转换功能可以转换为任意的数据类型,如基本数据类型Int,String,Strings,或者结构体Struct等等。

四、示例

目录结构

D:.
│  go.mod
│  go.sum
│  main.go
│
└─config
        config.toml

main.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

# Redis数据库配置
[redis]
    default = "127.0.0.1:6379,0"
    cache   = "127.0.0.1:6379,1,123456?idleTimeout=600"

02.goframe基础环境搭建.md

md

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

# 打开cmd设置
set GOPATH=D:\Project\GOPATH
set GOROOT=D:\Project\GO
set PATH=%PATH%;%GOROOT%\bin

当然应该将这些环境变量配置到系统环境变量中

  1. 此时打开cmd窗口,运行go version即可展示安装golang版本

# 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

3) 了解go modules

go.mod`是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

module hello

go 1.14

require github.com/gogf/gf v1.11.7

hello.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

module hello

go 1.14

require github.com/gogf/gf v1.11.7

main.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应用开发就是如此简单~!~

ReadMe.md

md

GoFrame基础教程-快速入门

教程目录

10.goframe常用工具介绍.md

md

Goframe常用工具介绍

一、gstr字符串处理

字符串处理工具类

示例

    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容器:HashMapTreeMapListMap

类型数据结构平均复杂度支持排序有序遍历说明
HashMap哈希表O(1)高性能读写操作,内存占用较高,随机遍历
ListMap哈希表+双向链表O(2)支持按照写入顺序遍历,内存占用较高
TreeMap红黑树O(log N)内存占用紧凑,支持键名排序及有序遍历

此外,gmap模块支持多种以哈希表为基础数据结构的常见类型map定义:IntIntMapIntStrMapIntAnyMapStrIntMapStrStrMapStrAnyMap

使用场景

任何map/哈希表/关联数组使用场景,尤其是并发安全场景中。

示例

    // 常规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对象,并支持常见数据格式的解析和转换等特点。

示例

    // 创建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算法

示例

    p := fmt.Println
    // md5加密
    p(gmd5.MustEncrypt("123456"))

五、类型转换

gconv

gf框架提供了非常强大的类型转换包gconv,可以实现将任何数据类型转换为指定的数据类型,对常用基本数据类型之间的无缝转换,同时也支持任意类型到struct对象的转换。由于gconv模块内部大量使用了断言而非反射(仅struct转换使用到了反射),因此执行的效率非常高。

Map转换

gconv.Map支持将任意的mapstruct/*struct类型转换为常用的 map[string]interface{} 类型。当转换参数为struct/*struct类型时,支持自动识别structc/gconv/json 标签,并且可以通过Map方法的第二个参数tags指定自定义的转换标签,以及多个标签解析的优先级。如果转换失败,返回nil

属性标签:当转换struct/*struct类型时, c/gconv/json 标签,也支持 -omitempty 标签属性。当使用 - 标签属性时,表示该属性不执行转换;当使用 omitempty 标签属性时,表示当属性为空时(空指针nil, 数字0, 字符串"", 空数组[]等)不执行转换。具体请查看随后示例。

Struct转换

项目中我们经常会遇到大量struct的使用,以及各种数据类型到struct的转换/赋值(特别是json/xml/各种协议编码转换的时候)。为提高编码及项目维护效率,gconv模块为各位开发者带来了极大的福利,为数据解析提供了更高的灵活度。

示例

    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

  • WX交流群:微信添加389961817备注GF加群

01.goframe介绍.md

md

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地址

目录结构及基本介绍:

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

六、GF交流

  • QQ交流群:116707870

  • WX交流群:微信添加389961817备注GF加群

03.goframe的WEB服务介绍.md

md

GoFrame的Web服务介绍

GF框架提供了非常强大的WebServer,由ghttp模块实现。实现了丰富完善的相关组件,例如:Router、Cookie、Session、路由注册、配置管理、模板引擎、缓存控制等等,支持热重启、热更新、多域名、多端口、多实例、HTTPS、Rewrite等等特性。

一、web基本介绍

我们的电脑浏览器(Browser)就是客户端(Client),大型的服务器就是服务端(Server);浏览器发送HTTP请求,即客户端通过网络将需求发给服务端,然后服务端也是通过网络将数据发给客户端;

image-20200319225300542

二、GF搭建web项目

这里主要介绍基本项目启动和配置参数

目录结构

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

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、模板引擎等等,非常强大便捷。

[server]
    # 端口号
    Address          = ":8199"
    # 静态目录
    ServerRoot       = "public"
    # 入口文件
    IndexFiles       = ["index.html", "main.html"]
    # 系统访问日志
    AccessLogEnabled = true
    # 系统异常日志panic
    ErrorLogEnabled  = true
    # 系统日志目录,启动,访问,异常
    LogPath          = "gflogs"

[logger]
    # 标准日志目录
    path   = "logs"
    # 日志级别
    level  = "all"

01.goframe介绍.assets

08.goframe数据库操作.md

md

Goframe数据库操作

一、基本介绍

gf框架的ORM功能由gdb模块实现,用于常用关系型数据库的ORM操作。其最大的特色在于同时支持mapstruct两种方式操作数据库。gdb默认情况下使用的是map数据类型作为基础的数据表记录载体,开发者无需预先定义数据表记录struct便可直接对数据表记录执行各种操作。这样的设计赋予了开发者更高的灵活度和简便性。

支持的数据库类型:Mysql,SQLite,PostgreSQL,SQLServer,Oracle

二、配置文件

推荐使用配置文件及单例对象来管理和使用数据库操作。

如果我们使用g对象管理模块中的g.DB("数据库分组名称")方法获取数据库操作对象,数据库对象将会自动读取config.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"

注意每一项分组配置均可以是多个节点,支持负载均衡权重策略。如果不使用多节点负载均衡特性,仅使用配置分组特性,也可以简化为如下格式:

[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"

如果仅仅是单数据库节点,不使用配置分组特性,那么也可以简化为如下格式:

[database]
    link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test"

不同数据类型对应的linkinfo如下:

数据库类型Linkinfo配置更多参数
mysqlmysql: 账号:密码@tcp(地址:端口)/数据库名称mysql
pgsqlpgsql: user=账号 password=密码 host=地址 port=端口 dbname=数据库名称pq
mssqlmssql: user id=账号;password=密码;server=地址;port=端口;database=数据库名称;encrypt=disablego-mssqldb
sqlitesqlite: 文件绝对路径 (如: /var/lib/db.sqlite3)go-sqlite3
oracleoracle: 账号/密码@地址:端口/数据库名称go-oci8

三、日志输出配置

gdb支持日志输出,内部使用的是glog.Logger对象实现日志管理,并且可以通过配置文件对日志对象进行配置。默认情况下gdb关闭了DEBUG日志输出,如果需要打开DEBUG信息需要将数据库的debug参数设置为true。以下是为一个配置文件示例:

[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种基本的数据类型:

type Map         map[string]interface{} // 数据记录
type List        []Map                  // 数据记录列表

type Value       *gvar.Var              // 返回数据表记录值
type Record      map[string]Value       // 返回数据表记录键值对
type Result      []Record               // 返回数据表记录列表
  1. MapList用于ORM操作过程中的输入参数类型(与全局类型g.Mapg.List一致,在项目开发中常用g.Mapg.List替换);

  2. Value/Record/Result用于ORM操作的结果数据类型;

五、数据库操作

Insert/Replace/Save

这三个链式操作方法用于数据的写入,并且支持自动的单条或者批量的数据写入,三者区别如下:

  1. Insert

使用INSERT INTO语句进行数据库写入,如果写入的数据中存在主键或者唯一索引时,返回失败,否则写入一条新数据;

  1. Replace

使用REPLACE INTO语句进行数据库写入,如果写入的数据中存在主键或者唯一索引时,会删除原有的记录,必定会写入一条新记录;

  1. Save

使用INSERT INTO语句进行数据库写入,如果写入的数据中存在主键或者唯一索引时,更新原有数据,否则写入一条新数据;

在部分数据库类型中,并不支持Replace/Save方法

Update更新方法

Update用于数据的更新,往往需要结合DataWhere方法共同使用。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方法的参数中至少应该输入字段参数。

数据库表

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 ;

示例

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()
}

02.goframe基础环境搭建.assets

03.goframe的WEB服务介绍.assets

doc_gf_tool_chain

19.GoFrame工具链之代码生成.md

md

GoFrame工具链之代码生成

代码生成gen之model生成

现在gen命令主要是生成model

模型生成采用了Active Record设计模式。该命令将会根据数据表名生成对应的目录,该目录名称即数据表包名。目录下自动生成3个文件:

  1. 数据表名.go 自定义文件,开发者可以自由定义填充的代码文件,仅会生成一次,每一次模型生成不会覆盖。

  2. 数据表名_entity.go 表结构文件,根据数据表结构生成的结构体定义文件,包含字段注释。数据表在外部变更后,可使用gen命令重复生成更新该文件。

  3. 数据表名_model.go 表模型文件,为数据表提供了许多便捷的CURD操作方法,并可直接查询返回该表的结构体对象。数据表在外部变更后,可使用gen命令重复生成更新该文件。

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

18.GoFrame工具链之项目构建.md

md

GoFrame工具链之项目构建

项目结构

推荐的Go业务型项目目录结构如下:

/
├── 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项目文档,如: 设计文档、帮助文档等等。
i18nI18N国际化I18N国际化配置文件目录。
library公共库包公共的功能封装包,往往不包含业务需求实现。
public静态目录仅有该目录下的文件才能对外提供静态服务访问。
router路由注册用于路由统一的注册管理。
template模板文件MVC模板文件存放的目录。
vendor第三方包第三方依赖包存放目录(可选, 未来会被淘汰)。
Dockerfile镜像描述云原生时代用于编译生成Docker镜像的描述文件。
go.mod依赖管理使用Go Module包管理的依赖描述文件。
main.go入口文件程序入口文件。

在实践中,小伙伴们可以根据实际情况增删目录。

初始化项目init

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

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

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

ReadMe.md

md

GoFrame工具链

教程目录

17.GoFrame工具链之基本介绍.md

md

GoFrame工具链之基本介绍

GF工具链介绍

GF为GoFrame辅助工具链,地址:https://github.com/gogf/gf-cli;下载对应平台

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

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

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

20.GoFrame工具链之其他命令.md

md

GoFrame工具链之其他命令

获取包get

go.mod

module gfcli

go 1.13

获取包

$ gf get github.com/gogf/gf
go: github.com/gogf/gf upgrade => v1.12.2

go.mod

module gfcli

require github.com/gogf/gf v1.12.2 // indirect

go 1.13

打二进制包pack

$ 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,内容省略

package boot

import "github.com/gogf/gf/os/gres"

func init() {
    if err := gres.Add("1f8b0800000000000"); err != nil {
        panic(err)
    }
}

通过gres.Dump()打印

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

$ 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文档

$ 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.

doc_login

13.gsession.assets

12.GoFrame登录实战之登录流程.md

md

GoFrame登录实战之登录流程

一、登录介绍

登录简单来说就是客户端输入账号密码,服务端进行账号密码验证,通过可访问系统,不通过停留在登录页面不能访问系统;登录看似简单的流程,但是实际还是有许多知识点是值得我们学习的;

二、登录流程图

下面我们来简单介绍一下设计相关的线框图,流程图和时序图;这里我使用的visio,大家也可以使用亿图,在线ProcessOn。

基本流程图

image-20200413235318994

基本流程图主要就是开始结束,流程处理,判断判断,数据,文档等;

UML序列

image-20200413235702664

UML时序图主要是对象,请求,响应,自关联,循环,可选;

image-20200414000541863

示例

image-20200414005122076

三、GoFrame登录示例

目录结构

:.
│  go.mod
│  go.sum
│  main.go
│
├─config
│      config.toml
│
└─template
        index.html
        user_index.html

go.mod

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

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <!-- import CSS -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <style>
        .el-row {
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
<div id="app">
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <span>${ .title }</span>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="6" :offset="6">
            <el-input v-model="username" placeholder="请输入内容"></el-input>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="6" :offset="6">
            <el-input placeholder="请输入密码" v-model="password" show-password></el-input>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <el-button @click="login">登录</el-button>
        </el-col>
    </el-row>


</div>
</body>
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>

<script>
    new Vue({
        el: '#app',
        data: function () {
            return {
                visible: false,
                username: '',
                password: ''
            }
        },
        methods: {
            login: function () {
                axios.post('/login', {       // 还可以直接把参数拼接在url后边
                    username: this.username,
                    password: this.password
                }).then(function (res) {
                    console.log(res.data)
                    if (res.data.code == 0) {
                        alert(res.data.msg)
                        window.location.href = "/user/index"
                    } else {
                        alert("失败:" + res.data.msg)
                    }
                }).catch(function (error) {
                    console.log(error);
                });
            }
        }
    })
</script>
</html>

页面user_index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <!-- import CSS -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <style>
        .el-row {
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
<div id="app">
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <span>${ .title }</span>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="24">
            <template>
                <el-table
                        :data="tableData"
                        style="width: 100%">
                    <el-table-column
                            prop="date"
                            label="日期"
                            width="180">
                    </el-table-column>
                    <el-table-column
                            prop="name"
                            label="姓名"
                            width="180">
                    </el-table-column>
                    <el-table-column
                            prop="address"
                            label="地址">
                    </el-table-column>
                </el-table>
            </template>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <el-button @click="logout">登出</el-button>
        </el-col>
    </el-row>


</div>
</body>
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>

<script>
    ${/*
    tableData: [{
        date: '2016-05-02',
        name: '王小虎',
        address: '上海市普陀区金沙江路 1518 弄'
    }]
    */}

    var listData = new Array();
    var data;
    ${range $index, $elem := .dataList}
        data = {};
        ${range $key, $value := $elem}
        data['${$key}'] = '${$value}'
        ${end}
        listData.push(data)
    ${end}

    var vm = new Vue({
        el: '#app',
        data: {
            visible: false,
            tableData: listData
        },
        methods: {
            logout: function () {
                axios.post('/logout', {}).then(function (res) {
                    console.log(res.data)
                    if (res.data.code == 0) {
                        alert(res.data.msg)
                        window.location.href = "/"
                    } else {
                        alert("失败:" + res.data.msg)
                    }
                }).catch(function (error) {
                    console.log(error);
                });
            }
        },
        mounted: function () {

        }
    })
</script>
</html>

16.secure.assets

16.GoFrame登录实战之登录安全.md

md

GoFrame登录实战之登录安全

从整体上看,HTTP就是一个通用的单纯协议机制。因此它具备较多优势,但是在安全性方面则呈劣势。

HTTP的不足

●通信使用明文(不加密),内容可能会被窃听

●不验证通信方的身份,因此有可能遭遇伪装

●无法证明报文的完整性,所以有可能已遭篡改

image-20200419001945127

一、在浏览器端HTTP是可以随意修改的

在Web应用中,从浏览器那接收到的HTTP请求的全部内容,都可以在客户端自由地变更、篡改。所以Web应用可能会接收到与预期数据不相同的内容。

客户端校验只是为了用户体验,要保证安全性就一定要做服务端校验;

二、避免传输拦截

传输参数进行加密:前端密码进行MD5不可逆加密;

传输使用https协议。

三、数据库泄露

安全存储用户密码的原则是:如果网站数据泄露了,密码也不能被还原。

简单的方式是通过md5 多层加密及加盐。比如:

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)

六、示例

目录

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

# 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

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

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

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")
    }
}

ReadMe.md

md

GoFrame实战-登录

教程目录

15.GoFrame登录实战之数据校验.md

md

GoFrame登录实战之数据校验

一、校验规则

gvalid模块实现了非常强大的数据校验功能,封装了40种常用的校验规则,支持单数据多规则校验、多数据多规则批量校验、自定义错误信息、自定义正则校验、支持struct tag规则及提示信息绑定等特性,是目前功能最强大的Go数据校验模块。

校验规则:

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

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())

}

14.GoFrame登录实战之session实现.md

md

GoFrame登录实战之session实现

一、概念介绍

GF框架提供了完善的Session管理能力,由gsession模块实现。由于Session机制在HTTP服务中最常用,因此后续章节中将着重以HTTP服务为示例介绍Session的使用。

二、存储实现方式

gsession实现并为开发者提供了常见的三种Session存储实现方式:

  1. 基于文件存储(默认):单节点部署方式下比较高效的持久化存储方式;

  2. 基于纯内存存储:性能最高效,但是无法持久化保存,重启即丢失;

  3. 基于Redis存储:远程Redis节点存储Session数据,支持应用多节点部署;

代码:

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()),
})

三、示例

目录

:.
│  go.mod
│  go.sum
│  main.go
│
├─config
│      config.toml
│
├─gession
│  └─default
│          C1YHTZWK7PS0AEN9VA
│
├─template
│      index.html
│      user_index.html
│
└─test
        test.http

main.go

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

# 账号
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

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <!-- import CSS -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <style>
        .el-row {
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
<div id="app">
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <span>${ .title }</span>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="6" :offset="6">
            <el-input v-model="username" placeholder="请输入内容"></el-input>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="6" :offset="6">
            <el-input placeholder="请输入密码" v-model="password" show-password></el-input>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <el-button @click="login">登录</el-button>
        </el-col>
    </el-row>


</div>
</body>
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>

<script>
    new Vue({
        el: '#app',
        data: function () {
            return {
                visible: false,
                username: '',
                password: ''
            }
        },
        methods: {
            login: function () {
                axios.post('/login', {       // 还可以直接把参数拼接在url后边
                    username: this.username,
                    password: this.password
                }).then(function (res) {
                    console.log(res.data)
                    if (res.data.code == 0) {
                        alert(res.data.msg)
                        window.location.href = "/user/index"
                    } else {
                        alert("失败:" + res.data.msg)
                    }
                }).catch(function (error) {
                    console.log(error);
                });
            }
        }
    })
</script>
</html>

user_index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <!-- import CSS -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <style>
        .el-row {
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
<div id="app">
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <span>${ .title }</span>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="24">
            <template>
                <el-table
                        :data="tableData"
                        style="width: 100%">
                    <el-table-column
                            prop="date"
                            label="日期"
                            width="180">
                    </el-table-column>
                    <el-table-column
                            prop="name"
                            label="姓名"
                            width="180">
                    </el-table-column>
                    <el-table-column
                            prop="address"
                            label="地址">
                    </el-table-column>
                </el-table>
            </template>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <el-button @click="logout">登出</el-button>
        </el-col>
    </el-row>


</div>
</body>
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>

<script>
    ${/*
    tableData: [{
        date: '2016-05-02',
        name: '王小虎',
        address: '上海市普陀区金沙江路 1518 弄'
    }]
    */}

    var listData = new Array();
    var data;
    ${range $index, $elem := .dataList}
    data = {};
    ${range $key, $value := $elem}
    data['${$key}'] = '${$value}'
    ${end}
    listData.push(data)
    ${end}

    var vm = new Vue({
        el: '#app',
        data: {
            visible: false,
            tableData: listData
        },
        methods: {
            logout: function () {
                axios.post('/user/logout', {}).then(function (res) {
                    console.log(res.data)
                    if (res.data.code == 0) {
                        alert(res.data.msg)
                        window.location.href = "/"
                    } else {
                        alert("失败:" + res.data.msg)
                    }
                }).catch(function (error) {
                    console.log(error);
                });
            }
        },
        mounted: function () {

        }
    })
</script>
</html>

test.http

POST http://127.0.0.1:8199/user/list
#Cookie: MySessionId=C1YHEOJ167MSBFJ5K1

###

12.login.assets

13.GoFrame登录实战之cookie和session.md

md

GoFrame登录实战之cookie&session

一、HTTP

超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。http是一个简单的请求-响应协议,它通常运行在TCP之上。HTTP是无状态的。

二、cookie

Cookie,有时也用其复数形式 Cookies。类型为“小型文本文件”,是某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息 。

Cookie 是一个请求首部,其中含有先前由服务器通过 Set-Cookie 首部投放并存储到客户端的 HTTP cookies。

这个首部可能会被完全移除,例如在浏览器的隐私设置里面设置为禁用cookie。

Cookie: <cookie-list>
Cookie: name=value
Cookie: name=value; name2=value2; name3=value3

响应首部 Set-Cookie 被用来由服务器端向客户端发送 cookie

# 设置cookie
Set-Cookie: <cookie-name>=<cookie-value> 
# cookie的最长有效时间
Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>
# 在cookie失效之前需要经过的秒数;
# 假如二者 (指 Expires 和Max-Age) 均存在,那么 Max-Age 优先级更高
Set-Cookie: <cookie-name>=<cookie-value>; Max-Age=<non-zero-digit>
# 指定 cookie 可以送达的主机名
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>
# 指定一个 URL 路径,这个路径必须出现在要请求的资源的路径中才可以发送 Cookie 首部
Set-Cookie: <cookie-name>=<cookie-value>; Path=<path-value>
# 一个带有安全属性的 cookie 只有在请求使用SSL和HTTPS协议的时候才会被发送到服务器
Set-Cookie: <cookie-name>=<cookie-value>; Secure
# 设置了 HttpOnly 属性的 cookie 不能使用 JavaScript 经由  Document.cookie 属性、XMLHttpRequest 和  Request APIs 进行访问,以防范跨站脚本攻击(XSS)
Set-Cookie: <cookie-name>=<cookie-value>; HttpOnly

三、session

Session:在计算机中,尤其是在网络应用中,称为“会话控制”。

Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。

四、sessionId

当用户发送请求的时候,服务器将用户cookie里面记录的session_id和服务器内存中存放的session_id进行比对,从而找到用户相对应的session进行操作。

在tomcat中session id中用JSESSIONID来表示;

五、cookie与session关系

image-20200412002802721

  1. 登录页面输入账号密码请求;服务端认证通过,存储session,设置Cookie;

  2. 请求资源需要的资源;服务端查看sessionId,判断sessionId是否存在,存在人为已登录返回资源;

11.GoFrame登录实战之模板引擎.md

md

GoFrame登录实战之模板引擎

这节课开始除了会介绍一部分GoFrame基础知识,也有一些关键的知识点和实战经验进行分享。示例还是主要以GoFrame为基础;

实践是检验真理的唯一标准。希望大家可以多跟练习,多去思考,多去体会,而不是简单的听;

一、模板引擎

模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档。

但模板引擎不属于特定技术领域,它是跨领域跨平台的概念。

模板配置config.toml

# 模板引擎配置
[viewer]
    Path        = "template"
    DefaultFile = "index.html"
    Delimiters  =  ["${", "}"]

模板使用

// 调用文件
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",
    })
})

模板常用标签

<!-- 取值 -->
{{ .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
*/}}

模板也支持函数,大家也可以自定义函数

${"我是中国人" | substr 2 -1}

其实模板可以当做一种语言来讲,这里不做过多介绍,一般使用模板只用一些基本功能,但是要想深入了解建议去看go模板和GoFram官网模板章节;

二、示例

目录

:.
│  go.mod
│  go.sum
│  main.go
│
├─config
│      config.toml
│
└─template
        index.html
        include.html

config.toml

# 模板引擎配置
[viewer]
    Path        = "template"
    DefaultFile = "index.html"
    Delimiters  =  ["${", "}"]

go.mod

module gf-template

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": "列表页面",
            "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

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <!-- import CSS -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <style>
        .el-row {
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
<div id="app">
    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <span>${ .title }</span>
            <span>${if .show}【展示】${end}</span>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="24">
            <template>
                <el-table
                        :data="tableData"
                        style="width: 100%">
                    <el-table-column
                            prop="date"
                            label="日期"
                            width="180">
                    </el-table-column>
                    <el-table-column
                            prop="name"
                            label="姓名"
                            width="180">
                    </el-table-column>
                    <el-table-column
                            prop="address"
                            label="地址">
                    </el-table-column>
                </el-table>
            </template>
        </el-col>
    </el-row>
    
    ${include "include.html" .}

</div>
</body>
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>

<script>
    /**
     tableData: [{
        date: '2016-05-02',
        name: '王小虎',
        address: '上海市普陀区金沙江路 1518 弄'
    }]
     */

    var listData = new Array();
    var data;
    ${range $index, $elem := .listData}
        data = {};
        ${range $key, $value := $elem}
        data['${$key}']='${$value}'
        ${end}
        listData.push(data)
    ${end}
    var vm = new Vue({
        el: '#app',
        data: {
            visible: false,
            tableData: listData
        }
    })
</script>
</html>

include.html

    <el-row>
        <el-col :span="6" :offset="6" style="text-align: center">
            <span style="font-weight: bold">这里是通过include引用的文件内容</span>
        </el-col>
    </el-row>

doc_regex

22.gregex正则详解.md

md

GoFrame实战之正则表达式语法详解

1. 正则介绍

1.1. 元字符

示例一 文本日期格式

[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

示例二 换行和所有字符匹配

123 abc
ABC

匹配全内容和换行符

正则1:\d+\s\w+\r\nABC
正则2:.*
正则3:[\w|\W]*

示例三 字符转义

123 abc
ABCW\*()

示例1:\d+\s\w+\r\nABCW\\\*\(\)

示例四 特殊字符

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

示例五 懒惰和贪婪模式

aabab

正则1:a.*?b

正则2:a.*b

正则3:a.+?b

正则4:a.??b

示例六 邮箱格式

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可以匹配任何一个字母或者数字或者下划线
WW大写,可以匹配任何一个字母或者数字或者下划线以外的字符
s可以匹配空格、制表符、换页符等空白字符的其中任意一个
SS大写,可以匹配任何一个空白字符以外的字符
d可以匹配任何一个 0~9 数字字符
DD大写,可以匹配任何一个非数字字符

2.2. 转义符

转义符说明
a响铃符 = x07
f换页符 = x0C
n换行符 = x0A
r回车符 = x0D
t制表符 = x09
v垂直制表符 = x0B
eESC 符 = x1B
b单词的开头或结尾,也就是单词的分界处
x20使用两位十六进制表示形式,可与该编号的字符匹配
u002B使用四位十六进制表示形式,可与该编号的字符匹配
x{20A060}使用任意位十六进制表示形式,可与该编号的字符匹配

2.3. 特殊字符

字符说明
^匹配输入字符串的开始位置。要匹配 "^" 字符本身,请使用 "^"
$匹配输入字符串的结尾位置。要匹配 "$" 字符本身,请使用 "$"
( )标记一个子表达式的开始和结束位置。要匹配小括号,请使用 "(" 和 ")"
[ ]用来自定义能够匹配 '多种字符' 的表达式。要匹配中括号,请使用 "[" 和 "]"
{ }修饰匹配次数的符号。要匹配大括号,请使用 "{" 和 "}"
.匹配除了换行符(n)以外的任意一个字符。要匹配小数点本身,请使用 "."
?修饰匹配次数为 0 次或 1 次。要匹配 "?" 字符本身,请使用 "?"
+修饰匹配次数为至少 1 次。要匹配 "+" 字符本身,请使用 "+"
*修饰匹配次数为 0 次或任意次。要匹配 "*" 字符本身,请使用 "*"
**\**左右两边表达式之间 "或" 关系。匹配 "\" 本身,请使用 "\"

2.4. 限定符

限定符说明
{n}表达式固定重复n次,比如:"w{2}" 相当于 "ww"
{m, n}表达式尽可能重复n次,至少重复m次:"ba{1,3}"可以匹配 "ba"或"baa"或"baaa"
{m, }表达式尽可能的多匹配,至少重复m次:"wd{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,并捕获文本到自动命名的组里
(?<name>exp)匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp)
(?:exp)匹配exp,不捕获匹配的文本,也不给此分组分配组号
(?=exp)匹配exp前面的位置
(?<=exp)匹配exp后面的位置
(?!exp)匹配后面跟的不是exp的位置
(?<!exp)匹配前面不是exp的位置
(?#comment)这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读

3.正则引擎通用命令列表

正则引擎一般有:

以下为各种正则引擎通用语法:

\ 转义单个元字符

[abc] 字符类

[^abc] 反义字符类

[a-z] 字符类范围

. 匹配换行符(\n)外所有字符

^ 字符串/行开始

$ 字符串/行结束

| 分支条件

? 0 ~ 1 次

* 0 次或更多次

+ 1 次或更多次

{n} n 次

{n,m} n ~ m 次

{n,} n 次或更多次

量词后加 ? 转为懒惰模式

(regex) 编号捕获组;\1 ~ \9 后向引用;


\d 代表数字 或 [:digit:] - 数字: '0 1 2 3 4 5 6 7 8 9'

\w 代表单词字符 或 [:alpha:] - 字母字符

\s 代表空白字符 或 [:blank:] - 空字符: 空格键符 和 制表符

注:编程语言一般都是d,linux系统命令基本都是[:digit:]

4.常用正则表达式

4.1. 校验数字的表达式

  1. 数字:^[0-9]*$

  2. n位的数字:^d{n}$

  3. 至少n位的数字:^d{n,}$

  4. m-n位的数字:^d{m,n}$

  5. 零和非零开头的数字:^(0|1-9*)$

  6. 非零开头的最多带两位小数的数字:^(1-9*)+(.[0-9]{1,2})?$

  7. 带1-2位小数的正数或负数:^(-)?d+(.d{1,2})?$

  8. 正数、负数、和小数:^(-|+)?d+(.d+)?$

  9. 有两位小数的正实数:^[0-9]+(.[0-9]{2})?$

  10. 有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$

  11. 非零的正整数:^[1-9]d$ 或 ^(1-9){1,3}$ 或 ^+?1-9*$

  12. 非零的负整数:^-[1-9][]0-9"$ 或 ^-[1-9]d$

  13. 非负整数:^d+$ 或 ^[1-9]d*|0$

  14. 非正整数:^-[1-9]d*|0$ 或 ^((-d+)|(0+))$

  15. 非负浮点数:^d+(.d+)?$ 或 ^[1-9]d.d|0.d[1-9]d|0?.0+|0$

  16. 非正浮点数:^((-d+(.d+)?)|(0+(.0+)?))$ 或 ^(-([1-9]d.d|0.d[1-9]d))|0?.0+|0$

  17. 正浮点数:^[1-9]d.d|0.d[1-9]d$ 或 ^(([0-9]+.[0-9]1-9)|([0-9]1-9.[0-9]+)|([0-9]1-9))$

  18. 负浮点数:^-([1-9]d.d|0.d[1-9]d)$ 或 ^(-(([0-9]+.[0-9]1-9)|([0-9]1-9.[0-9]+)|([0-9]1-9)))$

  19. 浮点数:^(-?d+)(.d+)?$ 或 ^-?([1-9]d.d|0.d[1-9]d|0?.0+|0)$

4.2. 校验字符的表达式

  1. 汉字:^[u4e00-u9fa5]{0,}$

  2. 英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$

  3. 长度为3-20的所有字符:^.{3,20}$

  4. 由26个英文字母组成的字符串:^[A-Za-z]+$

  5. 由26个大写英文字母组成的字符串:^[A-Z]+$

  6. 由26个小写英文字母组成的字符串:^[a-z]+$

  7. 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$

  8. 由数字、26个英文字母或者下划线组成的字符串:^w+$ 或 ^w{3,20}$

  9. 中文、英文、数字包括下划线:^[u4E00-u9FA5A-Za-z0-9_]+$

  10. 中文、英文、数字但不包括下划线等符号:^[u4E00-u9FA5A-Za-z0-9]+$ 或 ^[u4E00-u9FA5A-Za-z0-9]{2,20}$

  11. 可以输入含有^%&',;=?$"等字符:1+ 12 禁止输入含有~的字符:2+

4.3. 特殊需求表达式

  1. Email地址:^w+([-+.]w+)@w+([-.]w+).w+([-.]w+)*$

  2. 域名:a-zA-Z0-9{0,62}(/.a-zA-Z0-9{0,62})+/.?

  3. InternetURL:[a-zA-z]+://3 或 ^http://([w-]+.)+[w-]+(/[w-./?%&=])?$

  4. 手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])d{8}$

  5. 电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX):^((d{3,4}-)|d{3.4}-)?d{7,8}$

  6. 国内电话号码(0511-4405222、021-87888822):d{3}-d{8}|d{4}-d{7}

  7. 身份证号(15位、18位数字):^d{15}|d{18}$

  8. 短身份证号码(数字、字母x结尾):^([0-9]){7,18}(x|X)?$ 或 ^d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$

  9. 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^a-zA-Z{4,15}$

  10. 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]w{5,17}$

  11. 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.d)(?=.[a-z])(?=.*[A-Z]).{8,10}$

  12. 日期格式:^d{4}-d{1,2}-d{1,2}

  13. 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$

  14. 一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$

  15. xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\.x|X[l|L]$

  16. 中文字符的正则表达式:[u4e00-u9fa5]

  17. 双字节字符:4 (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))

  18. 空白行的正则表达式:nsr (可以用来删除空白行)

  19. HTML标记的正则表达式:<(S?)5>.?</1>|<.? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力)

  20. 首尾空白字符的正则表达式:^s|s$或(^s)|(s*$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)

  21. 腾讯QQ号:1-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))

21.gregex简介.md

md

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:

[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

输出结果:

王翦,110111111111,15311111111
李牧,110111111112,15311111112
廉颇,110111111113,15311111113
白起,110111111114,15311111114

示例二-文本处理

[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;

输出结果:

wangjian@sina.com
limu@qq.com
lipo@hotmail.com
baiqi@163.com
qishihuang@163.com.cn

示例三-GREP

grep 命令用于查找文件里符合条件的字符串。

grep基本命令:

# 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基本命令:

# 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分割功能:

[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基本命令

# 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':

[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的封装,极大地简化了正则的使用,并采用了解析缓存设计,提高了执行效率。

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])
}

执行后,输出结果为:

John says "GF" is the one he loves!

23.gregex使用.md

md

GoFrame实战之正则表达式使用

本章节主要讲解GoFrame中的正则表达式使用

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)
}

ReadMe.md

md

GoFrame实战-正则表达式

教程目录

目录

   └── 02.hello
       └── web
               ├── main.go
       └── hello
               ├── hello.go
   └── 03.web
       └── config
               ├── config.toml
       └── public
               ├── index.html
               ├── hello.html
       ├── main.go
   └── 04.router
       ├── test.http
       ├── main.go
   └── 05.client
       └── test
               ├── client_test.go
       ├── main.go
   └── 06.config
       └── configTest
               ├── config2.toml
               ├── config1.toml
       └── config
               ├── config.toml
       ├── config_test.go
       ├── main.go
   └── 07.log
       └── config
               ├── config.toml
       ├── main.go
   └── 08.database
       └── config
               ├── config.toml
       ├── db_test.go
   └── 09.redis
       └── config
               ├── config.toml
       ├── main.go
   └── 10.tools
       ├── tools_test.go
   └── 11.template
       └── config
               ├── config.toml
       └── template
               ├── index.html
               ├── include.html
       ├── main.go
   └── 12.login
       └── config
               ├── config.toml
       └── template
               ├── index.html
               ├── user_index.html
       ├── main.go
   └── 14.gsession
       └── test
               ├── test.http
       └── config
               ├── config.toml
       └── template
               ├── index.html
               ├── user_index.html
       ├── main.go
   └── 15.gvalid
       ├── valid_test.go
   └── 16.secure
       ├── bcrypt_test.go
       └── test
               ├── test.http
       └── config
               ├── server.key
               ├── server.crt
               ├── config.toml
       └── template
               ├── index.html
               ├── user_index.html
       └── public
               ├── md5.js
       ├── main.go
       └── sql
               ├── init.sql
   └── 17.gfcli
       └── docker
               ├── .gitkeep
       └── app
               └── model
                               ├── .gitkeep
                               └── user
                                                               ├── user_model.go
                                                               ├── user.go
                                                               ├── user_entity.go
               └── api
                               └── hello
                                                               ├── hello.go
               └── service
                               ├── .gitkeep
                               └── user
                                                               ├── userSvc.go
       └── boot
               ├── .gitkeep
               ├── boot.go
               ├── data.go
       └── config
               ├── .gitkeep
               ├── config.toml
       ├── Dockerfile
       └── template
               ├── .gitkeep
       └── document
               ├── .gitkeep
       ├── README.MD
       └── public
               └── plugin
                               ├── .gitkeep
               └── html
                               ├── .gitkeep
               └── resource
                               └── css
                                                               ├── .gitkeep
                               └── js
                                                               ├── .gitkeep
                               └── image
                                                               ├── .gitkeep
       └── i18n
               ├── .gitkeep
       ├── main.go
       └── router
               ├── .gitkeep
               ├── router.go
   └── 23.gregex
       └── test
               ├── gregex_test.go
   └── doc_basic
       ├── 04.goframe路由注册.md
       ├── 05.goframe的HTTP客户端.md
       ├── 06.goframe配置文件.md
       └── 05.goframe的HTTP客户端.assets
       ├── 07.goframe日志打印.md
       └── 04.goframe路由注册.assets
       ├── 09.goframeRedis操作.md
       ├── 02.goframe基础环境搭建.md
       ├── ReadMe.md
       ├── 10.goframe常用工具介绍.md
       ├── 01.goframe介绍.md
       ├── 03.goframe的WEB服务介绍.md
       └── 01.goframe介绍.assets
       ├── 08.goframe数据库操作.md
       └── 02.goframe基础环境搭建.assets
       └── 03.goframe的WEB服务介绍.assets
   └── doc_gf_tool_chain
       ├── 19.GoFrame工具链之代码生成.md
       ├── 18.GoFrame工具链之项目构建.md
       ├── ReadMe.md
       ├── 17.GoFrame工具链之基本介绍.md
       ├── 20.GoFrame工具链之其他命令.md
   └── doc_login
       └── 13.gsession.assets
       ├── 12.GoFrame登录实战之登录流程.md
       └── 16.secure.assets
       ├── 16.GoFrame登录实战之登录安全.md
       ├── ReadMe.md
       ├── 15.GoFrame登录实战之数据校验.md
       ├── 14.GoFrame登录实战之session实现.md
       └── 12.login.assets
       ├── 13.GoFrame登录实战之cookie和session.md
       ├── 11.GoFrame登录实战之模板引擎.md
   └── doc_regex
       ├── 22.gregex正则详解.md
       ├── 21.gregex简介.md
       ├── 23.gregex使用.md
       ├── ReadMe.md

  1. %&',;=?$x22
  2. ~x22
  3. s
  4. x00-xff
  5. >