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
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
对象的参数获取方法非常丰富,可以分为以下几类:
Get*
: 常用方法,简化参数获取,GetRequest*
的别名。GetQuery*
: 获取GET
方式传递过来的参数,包括Query String
及Body
参数解析。GetForm*
: 获取表单方式传递过来的参数,表单方式提交的参数Content-Type
往往为application/x-www-form-urlencoded
,application/form-data
,multipart/form-data
,multipart/mixed
等等。GetRequest*
: 获取客户端提交的参数,不区分提交方式。Get*Struct
: 将指定类型的请求参数绑定到指定的struct
对象上,注意给定的参数为对象指针。绝大部分场景中往往使用Parse
方法将请求数据转换为请求对象,具体详见后续章节。GetBody/GetBodyString
: 获取客户端提交的原始数据,该数据是客户端写入到body
中的原始数据,与HTTP Method
无关,例如客户端提交JSON/XML
数据格式时可以通过该方法获取原始的提交数据。GetJson
: 自动将原始请求信息解析为gjson.Json
对象指针返回。Exit*
: 用于请求流程退出控制;
响应Response
ghttp.Response
对象实现了标准库的http.ResponseWriter
接口。数据输出使用Write*
相关方法实现,并且数据输出采用了Buffer
机制,因此数据的处理效率比较高。任何时候可以通过OutputBuffer
方法输出缓冲区数据到客户端,并清空缓冲区数据。
简要说明:
Write*
方法用于数据的输出,可为任意的数据格式,内部通过断言对参数做自动分析。Write*Exit
方法用于数据输出后退出当前服务方法,可用于替代return
返回方法。WriteJson*
/WriteXml
方法用于特定数据格式的输出,这是为开发者提供的简便方法。WriteTpl*
方法用于模板输出,解析并输出模板文件,也可以直接解析并输出给定的模板内容。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是万维网的数据通信的基础。
请求:
响应:
放问GF启动的网址,通过Chrome F12查看NetWork中的URL;
优点:简单方便,浏览器支持完善,工具链成熟;
二、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
模块是并发安全的,仅提供配置文件读取功能,不提供数据写入/修改功能,支持的数据文件格式包括: JSON
、XML
、YAML/YML
、TOML
、INI
,项目中开发者可以灵活地选择自己熟悉的配置文件格式来进行配置管理。
默认读取执行文件所在目录及其下的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
开发框架的核心模块之一。
重要的几点说明:
glog
采用了无锁设计,性能高效;glog
支持文件输出、日志级别、日志分类、调试管理、调用跟踪、链式操作等等丰富特性;可以使用
glog.New
方法创建glog.Logger
对象用于自定义日志打印,也可以并推荐使用glog
默认提供的包方法来打印日志;当使用包方法修改模块配置时,注意任何的
glog.Set*
设置方法都将会全局生效;日志内容默认时间格式为
时间 [级别] 内容 换行
,其中时间
精确到毫秒级别,级别
为可选输出,内容
为调用端的参数输入,换行
为可选输出(部分方法自动为日志内容添加换行符号),日志内容示例:2018-10-10 12:00:01.568 [ERRO] 产生错误
;Print*/Debug*/Info*
方法输出日志内容到标准输出(stdout
),为防止日志的错乱,Notice*/Warning*/Error*/Critical*/Panic*/Fatal*
方法也是将日志内容输出到标准输出(stdout
);其中
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 | 是 | - | 端口 |
db | 否 | 0 | 数据库 |
pass | 否 | - | 授权密码 |
maxIdle | 否 | 0 | 允许限制的连接数(0表示不闲置) |
maxActive | 否 | 0 | 最大连接数量限制(0表示不限制) |
idleTimeout | 否 | 60 | 连接最大空闲时间(单位秒,不允许设置为0) |
maxConnLifetime | 否 | 60 | 连接最长存活时间(单位秒,不允许设置为0) |
其中的default
和cache
分别表示配置分组名称,我们在程序中可以通过该名称获取对应配置的redis对象。不传递分组名称时,默认使用redis.default
配置分组项)来获取对应配置的redis客户端单例对象。
三、结果处理
可以看到通过客户端方法Do/Receive
获取的数据都是二进制形式[]byte
的,需要开发者手动进行数据转换。
当然,gredis
模块也提供了DoVar/ReceiveVar
方法,用以获取可供方便转换的gvar.Var
通用变量结果。
通过gvar.Var
的强大转换功能可以转换为任意的数据类型,如基本数据类型Int
,String
,Strings
,或者结构体Struct
等等。
四、示例
目录结构
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为例:
去中文社区下载安装golang:https://studygolang.com/dl;
下载go.{version}.windows-amd64.msi或者go.{version}.windows-amd64.zip包,此次使用go.{version}.windows-amd64.zip包
解压压缩文件(这里使用的是D:Project,后面都基于这个目录)
配置环境变量GOPATH和GOROOT
# 打开cmd设置
set GOPATH=D:\Project\GOPATH
set GOROOT=D:\Project\GO
set PATH=%PATH%;%GOROOT%\bin
当然应该将这些环境变量配置到系统环境变量中
此时打开cmd窗口,运行
go version
即可展示安装golang版本
# go version
go version go1.14 windows/amd64
2)安装goland
官网下载goland:https://www.jetbrains.com/go/
安装注册购买或者破解;
首先打开File->Setting或者Ctrl+Alt+S,设置goroot和gopath,默认会获取环境变量配置
需要开启go modules功能,然后配置代理;不配置代理会访问国外地址,会很慢;建议使用以下三个地址:
https://goproxy.io
https://goproxy.cn
https://mirrors.aliyun.com/goproxy/
3) 了解go modules
go.mod`是Go项目的依赖描述文件:
module hello
go 1.14
require github.com/gogf/gf v1.11.7
module是配置项目名称
go配置的是使用的golang版本
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
容器:HashMap
、TreeMap
和ListMap
。
类型 | 数据结构 | 平均复杂度 | 支持排序 | 有序遍历 | 说明 |
---|---|---|---|---|---|
HashMap | 哈希表 | O(1) | 否 | 否 | 高性能读写操作,内存占用较高,随机遍历 |
ListMap | 哈希表+双向链表 | O(2) | 否 | 是 | 支持按照写入顺序遍历,内存占用较高 |
TreeMap | 红黑树 | O(log N) | 是 | 是 | 内存占用紧凑,支持键名排序及有序遍历 |
此外,
gmap
模块支持多种以哈希表为基础数据结构的常见类型map
定义:IntIntMap
、IntStrMap
、IntAnyMap
、StrIntMap
、StrStrMap
、StrAnyMap
。
使用场景:
任何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
支持将任意的map
或struct
/*struct
类型转换为常用的 map[string]interface{}
类型。当转换参数为struct
/*struct
类型时,支持自动识别struct
的 c/gconv/json
标签,并且可以通过Map
方法的第二个参数tags
指定自定义的转换标签,以及多个标签解析的优先级。如果转换失败,返回nil
。
属性标签:当转换
struct
/*struct
类型时,c/gconv/json
标签,也支持-
及omitempty
标签属性。当使用-
标签属性时,表示该属性不执行转换;当使用omitempty
标签属性时,表示当属性为空时(空指针nil
, 数字0
, 字符串""
, 空数组[]
等)不执行转换。具体请查看随后示例。
Struct转换
项目中我们经常会遇到大量struct
的使用,以及各种数据类型到struct
的转换/赋值(特别是json
/xml
/各种协议编码转换的时候)。为提高编码及项目维护效率,gconv
模块为各位开发者带来了极大的福利,为数据解析提供了更高的灵活度。
示例
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架构
六、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请求,即客户端通过网络将需求发给服务端,然后服务端也是通过网络将数据发给客户端;
二、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
操作。其最大的特色在于同时支持map
和struct
两种方式操作数据库。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配置 | 更多参数 |
---|---|---|
mysql | mysql: 账号:密码@tcp(地址:端口)/数据库名称 | mysql |
pgsql | pgsql: user=账号 password=密码 host=地址 port=端口 dbname=数据库名称 | pq |
mssql | mssql: user id=账号;password=密码;server=地址;port=端口;database=数据库名称;encrypt=disable | go-mssqldb |
sqlite | sqlite: 文件绝对路径 (如: /var/lib/db.sqlite3 ) | go-sqlite3 |
oracle | oracle: 账号/密码@地址:端口/数据库名称 | 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 // 返回数据表记录列表
Map
与List
用于ORM操作过程中的输入参数类型(与全局类型g.Map
和g.List
一致,在项目开发中常用g.Map
和g.List
替换);Value/Record/Result
用于ORM操作的结果数据类型;
五、数据库操作
Insert/Replace/Save
这三个链式操作方法用于数据的写入,并且支持自动的单条或者批量的数据写入,三者区别如下:
Insert
使用INSERT INTO
语句进行数据库写入,如果写入的数据中存在主键或者唯一索引时,返回失败,否则写入一条新数据;
Replace
使用REPLACE INTO
语句进行数据库写入,如果写入的数据中存在主键或者唯一索引时,会删除原有的记录,必定会写入一条新记录;
Save
使用INSERT INTO
语句进行数据库写入,如果写入的数据中存在主键或者唯一索引时,更新原有数据,否则写入一条新数据;
在部分数据库类型中,并不支持
Replace/Save
方法
Update
更新方法
Update
用于数据的更新,往往需要结合Data
及Where
方法共同使用。Data
方法用于指定需要更新的数据,Where
方法用于指定更新的条件范围。同时,Update
方法也支持直接给定数据和条件参数。
Delete
删除方法
Delete
方法用于数据的删除。
Where/And/Or
查询条件
这三个方法用于传递查询条件参数,支持的参数为任意的string/map/slice/struct/*struct
类型。
Where
条件参数推荐使用字符串的参数传递方式(并使用?
占位符预处理),因为map
/struct
类型作为查询参数无法保证顺序性,且在部分情况下(数据库有时会帮助你自动进行查询索引优化),数据库的索引和你传递的查询条件顺序有一定关系。
当使用多个Where
方法连接查询条件时,作用同And
。 此外,当存在多个查询条件时,gdb
会默认将多个条件分别使用()
符号进行包含,这种设计可以非常友好地支持查询条件分组。
All/One/Value/Count
数据查询
这四个方法是数据查询比较常用的方法:
All
用于查询并返回多条记录的列表/数组。One
用于查询并返回单条记录。Value
用于查询并返回一个字段值,往往需要结合Fields
方法使用。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个文件:
数据表名.go
自定义文件,开发者可以自由定义填充的代码文件,仅会生成一次,每一次模型生成不会覆盖。数据表名_entity.go
表结构文件,根据数据表结构生成的结构体定义文件,包含字段注释。数据表在外部变更后,可使用gen
命令重复生成更新该文件。数据表名_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项目文档,如: 设计文档、帮助文档等等。 |
i18n | I18N国际化 | 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。
基本流程图
基本流程图主要就是开始结束,流程处理,判断判断,数据,文档等;
UML序列
UML时序图主要是对象,请求,响应,自关联,循环,可选;
示例
三、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的不足
●通信使用明文(不加密),内容可能会被窃听
●不验证通信方的身份,因此有可能遭遇伪装
●无法证明报文的完整性,所以有可能已遭篡改
一、在浏览器端HTTP是可以随意修改的
在Web应用中,从浏览器那接收到的HTTP请求的全部内容,都可以在客户端自由地变更、篡改。所以Web应用可能会接收到与预期数据不相同的内容。
客户端校验只是为了用户体验,要保证安全性就一定要做服务端校验;
二、避免传输拦截
传输参数进行加密:前端密码进行MD5不可逆加密;
传输使用https协议。
三、数据库泄露
安全存储用户密码的原则是:如果网站数据泄露了,密码也不能被还原。
简单的方式是通过md5 多层加密及加盐。比如:
md5( md5( password + salt )[8:20] )
服务端数据库存储密码加密bcrypt
四、防止暴力破解
验证码防止暴力破解;
为用户体验,可多次相同ip或帐号错误,再进行验证码验证;
多次同一帐号错误,进行一段时间的帐号锁定。
五、常用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
二、校验方法
Check
方法用于单条数据校验,比较简单;CheckMap
方法用于多条数据校验,校验的主体变量为map
类型;CheckStruct
方法用于多条数据校验,校验的主体变量为结构体对象类型;Check*
方法只有在返回nil
的情况下,表示数据校验成功,否则返回校验出错的错误信息对象指针*Error
;
三、校验结果
校验结果为一个Error
对象指针。以下为对象方法:
FirstItem
在有多个键名/属性校验错误的时候,用以获取出错的第一个键名,以及其对应的出错规则和错误信息;其顺序性只有使用顺序校验规则时有效,否则返回的结果是随机的;FirstRule
会返回FirstItem
中得第一条出错的规则及错误信息;FirstString
会返回FirstRule
中得第一条规则错误信息;Map
会返回FirstItem
中得出错自规则及对应错误信息map
;Maps
会返回所有的出错键名及对应的出错规则及对应的错误信息(map[string]map[string]string
);String
会返回所有的错误信息,构成一条字符串返回,多个规则错误信息之间以;
符号连接;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
存储实现方式:
基于文件存储(默认):单节点部署方式下比较高效的持久化存储方式;
基于纯内存存储:性能最高效,但是无法持久化保存,重启即丢失;
基于
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关系
登录页面输入账号密码请求;服务端认证通过,存储session,设置Cookie;
请求资源需要的资源;服务端查看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 | 可以匹配任何一个字母或者数字或者下划线 |
W | W大写,可以匹配任何一个字母或者数字或者下划线以外的字符 |
s | 可以匹配空格、制表符、换页符等空白字符的其中任意一个 |
S | S大写,可以匹配任何一个空白字符以外的字符 |
d | 可以匹配任何一个 0~9 数字字符 |
D | D大写,可以匹配任何一个非数字字符 |
2.2. 转义符
转义符 | 说明 |
---|---|
a | 响铃符 = x07 |
f | 换页符 = x0C |
n | 换行符 = x0A |
r | 回车符 = x0D |
t | 制表符 = x09 |
v | 垂直制表符 = x0B |
e | ESC 符 = x1B |
b | 单词的开头或结尾,也就是单词的分界处 |
x20 | 使用两位十六进制表示形式,可与该编号的字符匹配 |
u002B | 使用四位十六进制表示形式,可与该编号的字符匹配 |
x{20A060} | 使用任意位十六进制表示形式,可与该编号的字符匹配 |
2.3. 特殊字符
字符 | 说明 | |||
---|---|---|---|---|
^ | 匹配输入字符串的开始位置。要匹配 "^" 字符本身,请使用 "^" | |||
$ | 匹配输入字符串的结尾位置。要匹配 "$" 字符本身,请使用 "$" | |||
( ) | 标记一个子表达式的开始和结束位置。要匹配小括号,请使用 "(" 和 ")" | |||
[ ] | 用来自定义能够匹配 '多种字符' 的表达式。要匹配中括号,请使用 "[" 和 "]" | |||
{ } | 修饰匹配次数的符号。要匹配大括号,请使用 "{" 和 "}" | |||
. | 匹配除了换行符(n)以外的任意一个字符。要匹配小数点本身,请使用 "." | |||
? | 修饰匹配次数为 0 次或 1 次。要匹配 "?" 字符本身,请使用 "?" | |||
+ | 修饰匹配次数为至少 1 次。要匹配 "+" 字符本身,请使用 "+" | |||
* | 修饰匹配次数为 0 次或任意次。要匹配 "*" 字符本身,请使用 "*" | |||
**\ | ** | 左右两边表达式之间 "或" 关系。匹配 "\ | " 本身,请使用 "\ | " |
2.4. 限定符
限定符 | 说明 |
---|---|
{n} | 表达式固定重复n次,比如:"w{2}" 相当于 "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. 校验数字的表达式
数字:^[0-9]*$
n位的数字:^d{n}$
至少n位的数字:^d{n,}$
m-n位的数字:^d{m,n}$
零和非零开头的数字:^(0|1-9*)$
非零开头的最多带两位小数的数字:^(1-9*)+(.[0-9]{1,2})?$
带1-2位小数的正数或负数:^(-)?d+(.d{1,2})?$
正数、负数、和小数:^(-|+)?d+(.d+)?$
有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
非零的正整数:^[1-9]d$ 或 ^(1-9){1,3}$ 或 ^+?1-9*$
非零的负整数:^-[1-9][]0-9"$ 或 ^-[1-9]d$
非负整数:^d+$ 或 ^[1-9]d*|0$
非正整数:^-[1-9]d*|0$ 或 ^((-d+)|(0+))$
非负浮点数:^d+(.d+)?$ 或 ^[1-9]d.d|0.d[1-9]d|0?.0+|0$
非正浮点数:^((-d+(.d+)?)|(0+(.0+)?))$ 或 ^(-([1-9]d.d|0.d[1-9]d))|0?.0+|0$
正浮点数:^[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))$
负浮点数:^-([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)))$
浮点数:^(-?d+)(.d+)?$ 或 ^-?([1-9]d.d|0.d[1-9]d|0?.0+|0)$
4.2. 校验字符的表达式
汉字:^[u4e00-u9fa5]{0,}$
英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
长度为3-20的所有字符:^.{3,20}$
由26个英文字母组成的字符串:^[A-Za-z]+$
由26个大写英文字母组成的字符串:^[A-Z]+$
由26个小写英文字母组成的字符串:^[a-z]+$
由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
由数字、26个英文字母或者下划线组成的字符串:^w+$ 或 ^w{3,20}$
中文、英文、数字包括下划线:^[u4E00-u9FA5A-Za-z0-9_]+$
中文、英文、数字但不包括下划线等符号:^[u4E00-u9FA5A-Za-z0-9]+$ 或 ^[u4E00-u9FA5A-Za-z0-9]{2,20}$
4.3. 特殊需求表达式
Email地址:^w+([-+.]w+)@w+([-.]w+).w+([-.]w+)*$
域名:a-zA-Z0-9{0,62}(/.a-zA-Z0-9{0,62})+/.?
InternetURL:[a-zA-z]+://3 或 ^http://([w-]+.)+[w-]+(/[w-./?%&=])?$
手机号码:^(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}$
电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX):^((d{3,4}-)|d{3.4}-)?d{7,8}$
国内电话号码(0511-4405222、021-87888822):d{3}-d{8}|d{4}-d{7}
身份证号(15位、18位数字):^d{15}|d{18}$
短身份证号码(数字、字母x结尾):^([0-9]){7,18}(x|X)?$ 或 ^d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$
帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^a-zA-Z{4,15}$
密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]w{5,17}$
强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.d)(?=.[a-z])(?=.*[A-Z]).{8,10}$
日期格式:^d{4}-d{1,2}-d{1,2}
一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$
一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$
xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\.x|X[l|L]$
中文字符的正则表达式:[u4e00-u9fa5]
双字节字符:4 (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))
空白行的正则表达式:nsr (可以用来删除空白行)
HTML标记的正则表达式:<(S?)5>.?</1>|<.? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力)
首尾空白字符的正则表达式:^s|s$或(^s)|(s*$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)
腾讯QQ号:1-9{4,} (腾讯QQ号从10000开始)
中国邮政编码:[1-9]d{5}(?!d) (中国邮政编码为6位数字)
IP地址:d+.d+.d+.d+ (提取IP地址时有用)
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}]
将
\[2020.*Utils is call \{name=
替换为空,然后将idcard=
、phone=
和}]
替换为空将
\[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]
将
\[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
评论已关闭