mirror of
https://gitee.com/johng/gf
synced 2026-06-19 23:02:56 +08:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 506552c3a9 | |||
| 2bacc77224 | |||
| bc53f265af | |||
| 344f232c36 | |||
| 27b677b0c0 | |||
| a5a0e381bd | |||
| d528d7f5ab | |||
| 821c71bd8d | |||
| 604a10400d | |||
| 9219471f67 | |||
| fe5d2e5685 | |||
| 0a89daa513 | |||
| 5dbda8aedc | |||
| 134e4cf28f | |||
| 56a85abef7 | |||
| 80c6ceaf26 | |||
| a10f428715 | |||
| 597f7468e9 | |||
| 5db8851213 |
@ -1,7 +1,7 @@
|
||||
|
||||
# MySQL数据库配置
|
||||
[database]
|
||||
debug = true
|
||||
# debug = true
|
||||
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true&loc=Local"
|
||||
|
||||
#[database]
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/database/gdb"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
@ -23,7 +21,7 @@ func main() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
db.SetDebug(true)
|
||||
//db.SetDebug(false)
|
||||
|
||||
glog.SetPath("/tmp")
|
||||
|
||||
@ -36,7 +34,4 @@ func main() {
|
||||
|
||||
db.Table("user").Data(g.Map{"name": "smith"}).Where("uid=?", 1).Save()
|
||||
|
||||
db.PrintQueriedSqls()
|
||||
|
||||
fmt.Println(db.GetLastSql())
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
|
||||
func main() {
|
||||
db := g.DB()
|
||||
|
||||
// 执行3条SQL查询
|
||||
for i := 1; i <= 3; i++ {
|
||||
db.Table("user").Where("id=?", i).One()
|
||||
|
||||
@ -7,9 +7,7 @@ import (
|
||||
func main() {
|
||||
s := ghttp.GetServer()
|
||||
s.BindHandler("/log/error", func(r *ghttp.Request) {
|
||||
if j := r.GetJson(); j != nil {
|
||||
r.Response.Write(j.Get("test"))
|
||||
}
|
||||
panic("OMG")
|
||||
})
|
||||
s.SetErrorLogEnabled(true)
|
||||
s.SetPort(8199)
|
||||
|
||||
17
.example/net/ghttp/server/request/basic.go
Normal file
17
.example/net/ghttp/server/request/basic.go
Normal file
@ -0,0 +1,17 @@
|
||||
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(r.Get("amount"))
|
||||
r.Response.Writeln(r.GetInt("amount"))
|
||||
r.Response.Writeln(r.GetFloat32("amount"))
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
18
.example/net/ghttp/server/request/exit/exit.go
Normal file
18
.example/net/ghttp/server/request/exit/exit.go
Normal file
@ -0,0 +1,18 @@
|
||||
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) {
|
||||
if r.GetInt("type") == 1 {
|
||||
r.Response.Writeln("john")
|
||||
}
|
||||
r.Response.Writeln("smith")
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
15
.example/net/ghttp/server/request/json-xml/test1.go
Normal file
15
.example/net/ghttp/server/request/json-xml/test1.go
Normal file
@ -0,0 +1,15 @@
|
||||
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.Writef("name: %v, pass: %v", r.Get("name"), r.Get("pass"))
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
47
.example/net/ghttp/server/request/json-xml/test2.go
Normal file
47
.example/net/ghttp/server/request/json-xml/test2.go
Normal file
@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/util/gvalid"
|
||||
)
|
||||
|
||||
type RegisterReq struct {
|
||||
Name string `p:"username" v:"required|length:6,30#请输入账号|账号长度为:min到:max位"`
|
||||
Pass string `p:"password1" v:"required|length:6,30#请输入密码|密码长度不够"`
|
||||
Pass2 string `p:"password2" v:"required|length:6,30|same:password1#请确认密码|两次密码不一致"`
|
||||
}
|
||||
|
||||
type RegisterRes struct {
|
||||
Code int `json:"code"`
|
||||
Error string `json:"error"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/register", func(r *ghttp.Request) {
|
||||
var req *RegisterReq
|
||||
//fmt.Println(r.GetBody())
|
||||
if err := r.Parse(&req); err != nil {
|
||||
// Validation error.
|
||||
if v, ok := err.(*gvalid.Error); ok {
|
||||
r.Response.WriteJsonExit(RegisterRes{
|
||||
Code: 1,
|
||||
Error: v.FirstString(),
|
||||
})
|
||||
}
|
||||
// Other error.
|
||||
r.Response.WriteJsonExit(RegisterRes{
|
||||
Code: 1,
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
// ...
|
||||
r.Response.WriteJsonExit(RegisterRes{
|
||||
Data: req,
|
||||
})
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
15
.example/net/ghttp/server/request/params/array.go
Normal file
15
.example/net/ghttp/server/request/params/array.go
Normal file
@ -0,0 +1,15 @@
|
||||
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.Write(r.Get("array"))
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
15
.example/net/ghttp/server/request/params/map.go
Normal file
15
.example/net/ghttp/server/request/params/map.go
Normal file
@ -0,0 +1,15 @@
|
||||
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.Write(r.Get("map"))
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
15
.example/net/ghttp/server/request/params/repeat.go
Normal file
15
.example/net/ghttp/server/request/params/repeat.go
Normal file
@ -0,0 +1,15 @@
|
||||
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.Write(r.Get("name"))
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
18
.example/net/ghttp/server/request/priority.go
Normal file
18
.example/net/ghttp/server/request/priority.go
Normal file
@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/input", func(r *ghttp.Request) {
|
||||
r.Response.Writeln(r.Get("amount"))
|
||||
})
|
||||
s.BindHandler("/query", func(r *ghttp.Request) {
|
||||
r.Response.Writeln(r.GetQuery("amount"))
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
@ -5,20 +5,20 @@ import (
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Uid int `json:"uid"`
|
||||
Name string `json:"name" params:"username"`
|
||||
Pass1 string `json:"pass1" params:"password1,userpass1"`
|
||||
Pass2 string `json:"pass2" params:"password3,userpass2"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
type User struct {
|
||||
Uid int `json:"uid"`
|
||||
Name string `json:"name" p:"username"`
|
||||
Pass1 string `json:"pass1" p:"password1"`
|
||||
Pass2 string `json:"pass2" p:"password2"`
|
||||
}
|
||||
|
||||
s := g.Server()
|
||||
s.BindHandler("/user", func(r *ghttp.Request) {
|
||||
user := new(User)
|
||||
r.GetToStruct(user)
|
||||
//r.GetPostToStruct(user)
|
||||
//r.GetQueryToStruct(user)
|
||||
var user *User
|
||||
if err := r.Parse(&user); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
r.Response.WriteJson(user)
|
||||
})
|
||||
s.SetPort(8199)
|
||||
|
||||
25
.example/net/ghttp/server/request/struct/parse1.go
Normal file
25
.example/net/ghttp/server/request/struct/parse1.go
Normal file
@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type User struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Pass1 string `json:"password1" p:"password1"`
|
||||
Pass2 string `json:"password2" p:"password2"`
|
||||
}
|
||||
s := g.Server()
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
var user *User
|
||||
if err := r.Parse(&user); err != nil {
|
||||
r.Response.WriteExit(err)
|
||||
}
|
||||
r.Response.WriteExit(user)
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
37
.example/net/ghttp/server/request/struct/parse2.go
Normal file
37
.example/net/ghttp/server/request/struct/parse2.go
Normal file
@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
type RegisterReq struct {
|
||||
Name string
|
||||
Pass string `p:"password1"`
|
||||
Pass2 string `p:"password2"`
|
||||
}
|
||||
|
||||
type RegisterRes struct {
|
||||
Code int `json:"code"`
|
||||
Error string `json:"error"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/register", func(r *ghttp.Request) {
|
||||
var req *RegisterReq
|
||||
if err := r.Parse(&req); err != nil {
|
||||
r.Response.WriteJsonExit(RegisterRes{
|
||||
Code: 1,
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
// ...
|
||||
r.Response.WriteJsonExit(RegisterRes{
|
||||
Data: req,
|
||||
})
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
37
.example/net/ghttp/server/request/validation/validation1.go
Normal file
37
.example/net/ghttp/server/request/validation/validation1.go
Normal file
@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
type RegisterReq struct {
|
||||
Name string `p:"username" v:"required|length:6,30#请输入账号|账号长度为:min到:max位"`
|
||||
Pass string `p:"password1" v:"required|length:6,30#请输入密码|密码长度不够"`
|
||||
Pass2 string `p:"password2" v:"required|length:6,30|same:password1#请确认密码|两次密码不一致"`
|
||||
}
|
||||
|
||||
type RegisterRes struct {
|
||||
Code int `json:"code"`
|
||||
Error string `json:"error"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/register", func(r *ghttp.Request) {
|
||||
var req *RegisterReq
|
||||
if err := r.Parse(&req); err != nil {
|
||||
r.Response.WriteJsonExit(RegisterRes{
|
||||
Code: 1,
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
// ...
|
||||
r.Response.WriteJsonExit(RegisterRes{
|
||||
Data: req,
|
||||
})
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
46
.example/net/ghttp/server/request/validation/validation2.go
Normal file
46
.example/net/ghttp/server/request/validation/validation2.go
Normal file
@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/util/gvalid"
|
||||
)
|
||||
|
||||
type RegisterReq struct {
|
||||
Name string `p:"username" v:"required|length:6,30#请输入账号|账号长度为:min到:max位"`
|
||||
Pass string `p:"password1" v:"required|length:6,30#请输入密码|密码长度不够"`
|
||||
Pass2 string `p:"password2" v:"required|length:6,30|same:password1#请确认密码|两次密码不一致"`
|
||||
}
|
||||
|
||||
type RegisterRes struct {
|
||||
Code int `json:"code"`
|
||||
Error string `json:"error"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/register", func(r *ghttp.Request) {
|
||||
var req *RegisterReq
|
||||
if err := r.Parse(&req); err != nil {
|
||||
// Validation error.
|
||||
if v, ok := err.(*gvalid.Error); ok {
|
||||
r.Response.WriteJsonExit(RegisterRes{
|
||||
Code: 1,
|
||||
Error: v.FirstString(),
|
||||
})
|
||||
}
|
||||
// Other error.
|
||||
r.Response.WriteJsonExit(RegisterRes{
|
||||
Code: 1,
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
// ...
|
||||
r.Response.WriteJsonExit(RegisterRes{
|
||||
Data: req,
|
||||
})
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
22
.example/net/ghttp/server/router/group/basic.go
Normal file
22
.example/net/ghttp/server/router/group/basic.go
Normal file
@ -0,0 +1,22 @@
|
||||
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("/all", func(r *ghttp.Request) {
|
||||
r.Response.Write("all")
|
||||
})
|
||||
group.GET("/get", func(r *ghttp.Request) {
|
||||
r.Response.Write("get")
|
||||
})
|
||||
group.POST("/post", func(r *ghttp.Request) {
|
||||
r.Response.Write("post")
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
38
.example/net/ghttp/server/router/group/batch.go
Normal file
38
.example/net/ghttp/server/router/group/batch.go
Normal file
@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
type Object struct{}
|
||||
|
||||
func (o *Object) Show(r *ghttp.Request) {
|
||||
r.Response.Writeln("Show")
|
||||
}
|
||||
|
||||
func (o *Object) Delete(r *ghttp.Request) {
|
||||
r.Response.Writeln("REST Delete")
|
||||
}
|
||||
|
||||
func Handler(r *ghttp.Request) {
|
||||
r.Response.Writeln("Handler")
|
||||
}
|
||||
|
||||
func HookHandler(r *ghttp.Request) {
|
||||
r.Response.Writeln("HOOK Handler")
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
obj := new(Object)
|
||||
s.Group("/api").Bind([]ghttp.GroupItem{
|
||||
{"ALL", "*", HookHandler, ghttp.HOOK_BEFORE_SERVE},
|
||||
{"ALL", "/handler", Handler},
|
||||
{"ALL", "/obj", obj},
|
||||
{"GET", "/obj/show", obj, "Show"},
|
||||
{"REST", "/obj/rest", obj},
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
)
|
||||
|
||||
func MiddlewareAuth(r *ghttp.Request) {
|
||||
@ -24,39 +23,37 @@ func MiddlewareCORS(r *ghttp.Request) {
|
||||
|
||||
func MiddlewareLog(r *ghttp.Request) {
|
||||
r.Middleware.Next()
|
||||
glog.Println(r.Response.Status, r.URL.Path)
|
||||
g.Log().Println(r.Response.Status, r.URL.Path)
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.Middleware(MiddlewareLog)
|
||||
})
|
||||
s.Use(MiddlewareLog)
|
||||
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
|
||||
group.Middleware(MiddlewareAuth, MiddlewareCORS)
|
||||
g.GET("/test", func(r *ghttp.Request) {
|
||||
group.GET("/test", func(r *ghttp.Request) {
|
||||
r.Response.Write("test")
|
||||
})
|
||||
g.Group("/order", func(group *ghttp.RouterGroup) {
|
||||
g.GET("/list", func(r *ghttp.Request) {
|
||||
group.Group("/order", func(group *ghttp.RouterGroup) {
|
||||
group.GET("/list", func(r *ghttp.Request) {
|
||||
r.Response.Write("list")
|
||||
})
|
||||
g.PUT("/update", func(r *ghttp.Request) {
|
||||
group.PUT("/update", func(r *ghttp.Request) {
|
||||
r.Response.Write("update")
|
||||
})
|
||||
})
|
||||
g.Group("/user", func(group *ghttp.RouterGroup) {
|
||||
g.GET("/info", func(r *ghttp.Request) {
|
||||
group.Group("/user", func(group *ghttp.RouterGroup) {
|
||||
group.GET("/info", func(r *ghttp.Request) {
|
||||
r.Response.Write("info")
|
||||
})
|
||||
g.POST("/edit", func(r *ghttp.Request) {
|
||||
group.POST("/edit", func(r *ghttp.Request) {
|
||||
r.Response.Write("edit")
|
||||
})
|
||||
g.DELETE("/drop", func(r *ghttp.Request) {
|
||||
group.DELETE("/drop", func(r *ghttp.Request) {
|
||||
r.Response.Write("drop")
|
||||
})
|
||||
})
|
||||
g.Group("/hook", func(group *ghttp.RouterGroup) {
|
||||
group.Group("/hook", func(group *ghttp.RouterGroup) {
|
||||
group.Hook("/*", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) {
|
||||
r.Response.Write("hook any")
|
||||
})
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
package main
|
||||
|
||||
import "github.com/gogf/gf/net/ghttp"
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := ghttp.GetServer()
|
||||
s := g.Server()
|
||||
s.BindHandler("/user/:name", func(r *ghttp.Request) {
|
||||
r.Response.Writeln(r.Router.Uri)
|
||||
})
|
||||
|
||||
16
.example/net/ghttp/server/template/conflicts-name/client.go
Normal file
16
.example/net/ghttp/server/template/conflicts-name/client.go
Normal file
@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
// https://github.com/gogf/gf/issues/437
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.WriteTpl("client/layout.html")
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
1
|
||||
@ -12,16 +12,18 @@ func main() {
|
||||
s.SetConfigWithMap(g.Map{
|
||||
"SessionMaxAge": time.Minute,
|
||||
})
|
||||
s.BindHandler("/set", func(r *ghttp.Request) {
|
||||
r.Session.Set("time", gtime.Second())
|
||||
r.Response.Write("ok")
|
||||
})
|
||||
s.BindHandler("/get", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.Session.Map())
|
||||
})
|
||||
s.BindHandler("/del", func(r *ghttp.Request) {
|
||||
r.Session.Clear()
|
||||
r.Response.Write("ok")
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.ALL("/set", func(r *ghttp.Request) {
|
||||
r.Session.Set("time", gtime.Timestamp())
|
||||
r.Response.Write("ok")
|
||||
})
|
||||
group.ALL("/get", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.Session.Map())
|
||||
})
|
||||
group.ALL("/del", func(r *ghttp.Request) {
|
||||
r.Session.Clear()
|
||||
r.Response.Write("ok")
|
||||
})
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
|
||||
@ -14,16 +14,18 @@ func main() {
|
||||
"SessionMaxAge": time.Minute,
|
||||
"SessionStorage": gsession.NewStorageMemory(),
|
||||
})
|
||||
s.BindHandler("/set", func(r *ghttp.Request) {
|
||||
r.Session.Set("time", gtime.Second())
|
||||
r.Response.Write("ok")
|
||||
})
|
||||
s.BindHandler("/get", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.Session.Map())
|
||||
})
|
||||
s.BindHandler("/del", func(r *ghttp.Request) {
|
||||
r.Session.Clear()
|
||||
r.Response.Write("ok")
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.ALL("/set", func(r *ghttp.Request) {
|
||||
r.Session.Set("time", gtime.Timestamp())
|
||||
r.Response.Write("ok")
|
||||
})
|
||||
group.ALL("/get", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.Session.Map())
|
||||
})
|
||||
group.ALL("/del", func(r *ghttp.Request) {
|
||||
r.Session.Clear()
|
||||
r.Response.Write("ok")
|
||||
})
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
|
||||
@ -14,16 +14,18 @@ func main() {
|
||||
"SessionMaxAge": time.Minute,
|
||||
"SessionStorage": gsession.NewStorageRedisHashTable(g.Redis()),
|
||||
})
|
||||
s.BindHandler("/set", func(r *ghttp.Request) {
|
||||
r.Session.Set("time", gtime.Second())
|
||||
r.Response.Write("ok")
|
||||
})
|
||||
s.BindHandler("/get", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.Session.Map())
|
||||
})
|
||||
s.BindHandler("/del", func(r *ghttp.Request) {
|
||||
r.Session.Clear()
|
||||
r.Response.Write("ok")
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.ALL("/set", func(r *ghttp.Request) {
|
||||
r.Session.Set("time", gtime.Timestamp())
|
||||
r.Response.Write("ok")
|
||||
})
|
||||
group.ALL("/get", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.Session.Map())
|
||||
})
|
||||
group.ALL("/del", func(r *ghttp.Request) {
|
||||
r.Session.Clear()
|
||||
r.Response.Write("ok")
|
||||
})
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
|
||||
@ -14,16 +14,18 @@ func main() {
|
||||
"SessionMaxAge": time.Minute,
|
||||
"SessionStorage": gsession.NewStorageRedis(g.Redis()),
|
||||
})
|
||||
s.BindHandler("/set", func(r *ghttp.Request) {
|
||||
r.Session.Set("time", gtime.Second())
|
||||
r.Response.Write("ok")
|
||||
})
|
||||
s.BindHandler("/get", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.Session.Map())
|
||||
})
|
||||
s.BindHandler("/del", func(r *ghttp.Request) {
|
||||
r.Session.Clear()
|
||||
r.Response.Write("ok")
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.ALL("/set", func(r *ghttp.Request) {
|
||||
r.Session.Set("time", gtime.Timestamp())
|
||||
r.Response.Write("ok")
|
||||
})
|
||||
group.ALL("/get", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.Session.Map())
|
||||
})
|
||||
group.ALL("/del", func(r *ghttp.Request) {
|
||||
r.Session.Clear()
|
||||
r.Response.Write("ok")
|
||||
})
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
|
||||
[database]
|
||||
debug = true
|
||||
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
|
||||
|
||||
[redis]
|
||||
default = "127.0.0.1:6379,0"
|
||||
|
||||
@ -2,12 +2,26 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := "aaaaa_post"
|
||||
b := "aaaaa_"
|
||||
c := gstr.TrimLeftStr(a, b)
|
||||
fmt.Println(c)
|
||||
type Base struct {
|
||||
Id int `c:"id"`
|
||||
CreateTime string `c:"create_time"`
|
||||
}
|
||||
type User struct {
|
||||
Base `c:"base"`
|
||||
Passport string `c:"passport"`
|
||||
Password string `c:"password"`
|
||||
Nickname string `c:"nickname"`
|
||||
}
|
||||
user := new(User)
|
||||
user.Id = 1
|
||||
user.Nickname = "John"
|
||||
user.Passport = "johng"
|
||||
user.Password = "123456"
|
||||
user.CreateTime = "2019"
|
||||
fmt.Println(gconv.Map(user))
|
||||
fmt.Println(gconv.MapDeep(user))
|
||||
}
|
||||
|
||||
11
.example/other/test2.go
Normal file
11
.example/other/test2.go
Normal file
@ -0,0 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := ghttp.PostContent("http://127.0.0.1:8199/test", `<doc><id>1</id><name>john</name><password1>123Abc!@#</password1><password2>123Abc!@#</password2></doc>`)
|
||||
fmt.Println(r)
|
||||
}
|
||||
@ -7,18 +7,18 @@ import (
|
||||
|
||||
func main() {
|
||||
type Ids struct {
|
||||
Id int `json:"id"`
|
||||
Uid int `json:"uid"`
|
||||
Id int `c:"id"`
|
||||
Uid int `c:"uid"`
|
||||
}
|
||||
type Base struct {
|
||||
Ids
|
||||
CreateTime string `json:"create_time"`
|
||||
CreateTime string `c:"create_time"`
|
||||
}
|
||||
type User struct {
|
||||
Base
|
||||
Passport string `json:"passport"`
|
||||
Password string `json:"password"`
|
||||
Nickname string `json:"nickname"`
|
||||
Passport string `c:"passport"`
|
||||
Password string `c:"password"`
|
||||
Nickname string `c:"nickname"`
|
||||
}
|
||||
user := new(User)
|
||||
user.Id = 1
|
||||
@ -27,5 +27,6 @@ func main() {
|
||||
user.Passport = "johng"
|
||||
user.Password = "123456"
|
||||
user.CreateTime = "2019"
|
||||
g.Dump(gconv.Map(user))
|
||||
g.Dump(gconv.MapDeep(user))
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
# Donators
|
||||
|
||||
We currently accept donation by Alipay/WechatPay, please note your github/gitee account in your payment bill.
|
||||
|
||||
|
||||
| Name | Channel | Amount | Comment
|
||||
|---|---|--- | ---
|
||||
@ -36,7 +38,10 @@
|
||||
|*洁|wechat|¥10.00|赞助你肥宅快乐水
|
||||
|R*s|wechat|¥18.88| 谢谢GF!辛苦了!
|
||||
|粟*e|wechat|¥50.00|
|
||||
|
||||
|[李超](https://github.com/effortlee)|wechat|¥124.00|
|
||||
|张炳贤|wechat+qq|¥600.00|
|
||||
|[王哈哈](https://gitee.com/develop1024)|wechat|¥6.66| 希望gf越来越好
|
||||
|夕景|alipay+qq|¥9.96+3.57|
|
||||
|
||||
|
||||
<img src="https://goframe.org/images/donate.png"/>
|
||||
|
||||
@ -625,6 +625,35 @@ func (a *Array) CountValues() map[interface{}]int {
|
||||
return m
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
func (a *Array) Iterator(f func(k int, v interface{}) bool) {
|
||||
a.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the array in ascending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (a *Array) IteratorAsc(f func(k int, v interface{}) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for k, v := range a.array {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the array in descending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (a *Array) IteratorDesc(f func(k int, v interface{}) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for i := len(a.array) - 1; i >= 0; i-- {
|
||||
if !f(i, a.array[i]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String returns current array as a string, which implements like json.Marshal does.
|
||||
func (a *Array) String() string {
|
||||
a.mu.RLock()
|
||||
|
||||
@ -637,6 +637,35 @@ func (a *IntArray) CountValues() map[int]int {
|
||||
return m
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
func (a *IntArray) Iterator(f func(k int, v int) bool) {
|
||||
a.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the array in ascending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (a *IntArray) IteratorAsc(f func(k int, v int) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for k, v := range a.array {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the array in descending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (a *IntArray) IteratorDesc(f func(k int, v int) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for i := len(a.array) - 1; i >= 0; i-- {
|
||||
if !f(i, a.array[i]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String returns current array as a string, which implements like json.Marshal does.
|
||||
func (a *IntArray) String() string {
|
||||
return "[" + a.Join(",") + "]"
|
||||
|
||||
@ -622,6 +622,35 @@ func (a *StrArray) CountValues() map[string]int {
|
||||
return m
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
func (a *StrArray) Iterator(f func(k int, v string) bool) {
|
||||
a.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the array in ascending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (a *StrArray) IteratorAsc(f func(k int, v string) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for k, v := range a.array {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the array in descending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (a *StrArray) IteratorDesc(f func(k int, v string) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for i := len(a.array) - 1; i >= 0; i-- {
|
||||
if !f(i, a.array[i]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String returns current array as a string, which implements like json.Marshal does.
|
||||
func (a *StrArray) String() string {
|
||||
a.mu.RLock()
|
||||
|
||||
@ -570,6 +570,35 @@ func (a *SortedArray) CountValues() map[interface{}]int {
|
||||
return m
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
func (a *SortedArray) Iterator(f func(k int, v interface{}) bool) {
|
||||
a.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the array in ascending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (a *SortedArray) IteratorAsc(f func(k int, v interface{}) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for k, v := range a.array {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the array in descending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (a *SortedArray) IteratorDesc(f func(k int, v interface{}) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for i := len(a.array) - 1; i >= 0; i-- {
|
||||
if !f(i, a.array[i]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String returns current array as a string, which implements like json.Marshal does.
|
||||
func (a *SortedArray) String() string {
|
||||
a.mu.RLock()
|
||||
|
||||
@ -562,6 +562,35 @@ func (a *SortedIntArray) CountValues() map[int]int {
|
||||
return m
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
func (a *SortedIntArray) Iterator(f func(k int, v int) bool) {
|
||||
a.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the array in ascending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (a *SortedIntArray) IteratorAsc(f func(k int, v int) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for k, v := range a.array {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the array in descending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (a *SortedIntArray) IteratorDesc(f func(k int, v int) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for i := len(a.array) - 1; i >= 0; i-- {
|
||||
if !f(i, a.array[i]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String returns current array as a string, which implements like json.Marshal does.
|
||||
func (a *SortedIntArray) String() string {
|
||||
return "[" + a.Join(",") + "]"
|
||||
|
||||
@ -547,6 +547,35 @@ func (a *SortedStrArray) CountValues() map[string]int {
|
||||
return m
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
func (a *SortedStrArray) Iterator(f func(k int, v string) bool) {
|
||||
a.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the array in ascending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (a *SortedStrArray) IteratorAsc(f func(k int, v string) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for k, v := range a.array {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the array in descending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (a *SortedStrArray) IteratorDesc(f func(k int, v string) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for i := len(a.array) - 1; i >= 0; i-- {
|
||||
if !f(i, a.array[i]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String returns current array as a string, which implements like json.Marshal does.
|
||||
func (a *SortedStrArray) String() string {
|
||||
a.mu.RLock()
|
||||
|
||||
@ -450,3 +450,50 @@ func TestArray_Json(t *testing.T) {
|
||||
gtest.Assert(user.Scores, data["Scores"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_Iterator(t *testing.T) {
|
||||
slice := g.Slice{"a", "b", "d", "c"}
|
||||
array := garray.NewArrayFrom(slice)
|
||||
gtest.Case(t, func() {
|
||||
array.Iterator(func(k int, v interface{}) bool {
|
||||
gtest.Assert(v, slice[k])
|
||||
return true
|
||||
})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
array.IteratorAsc(func(k int, v interface{}) bool {
|
||||
gtest.Assert(v, slice[k])
|
||||
return true
|
||||
})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
array.IteratorDesc(func(k int, v interface{}) bool {
|
||||
gtest.Assert(v, slice[k])
|
||||
return true
|
||||
})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
index := 0
|
||||
array.Iterator(func(k int, v interface{}) bool {
|
||||
index++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(index, 1)
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
index := 0
|
||||
array.IteratorAsc(func(k int, v interface{}) bool {
|
||||
index++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(index, 1)
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
index := 0
|
||||
array.IteratorDesc(func(k int, v interface{}) bool {
|
||||
index++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(index, 1)
|
||||
})
|
||||
}
|
||||
|
||||
@ -484,3 +484,50 @@ func TestIntArray_Json(t *testing.T) {
|
||||
gtest.Assert(user.Scores, data["Scores"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_Iterator(t *testing.T) {
|
||||
slice := g.SliceInt{10, 20, 30, 40}
|
||||
array := garray.NewIntArrayFrom(slice)
|
||||
gtest.Case(t, func() {
|
||||
array.Iterator(func(k int, v int) bool {
|
||||
gtest.Assert(v, slice[k])
|
||||
return true
|
||||
})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
array.IteratorAsc(func(k int, v int) bool {
|
||||
gtest.Assert(v, slice[k])
|
||||
return true
|
||||
})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
array.IteratorDesc(func(k int, v int) bool {
|
||||
gtest.Assert(v, slice[k])
|
||||
return true
|
||||
})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
index := 0
|
||||
array.Iterator(func(k int, v int) bool {
|
||||
index++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(index, 1)
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
index := 0
|
||||
array.IteratorAsc(func(k int, v int) bool {
|
||||
index++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(index, 1)
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
index := 0
|
||||
array.IteratorDesc(func(k int, v int) bool {
|
||||
index++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(index, 1)
|
||||
})
|
||||
}
|
||||
|
||||
@ -488,3 +488,50 @@ func TestStrArray_Json(t *testing.T) {
|
||||
gtest.Assert(user.Scores, data["Scores"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestStrArray_Iterator(t *testing.T) {
|
||||
slice := g.SliceStr{"a", "b", "d", "c"}
|
||||
array := garray.NewStrArrayFrom(slice)
|
||||
gtest.Case(t, func() {
|
||||
array.Iterator(func(k int, v string) bool {
|
||||
gtest.Assert(v, slice[k])
|
||||
return true
|
||||
})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
array.IteratorAsc(func(k int, v string) bool {
|
||||
gtest.Assert(v, slice[k])
|
||||
return true
|
||||
})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
array.IteratorDesc(func(k int, v string) bool {
|
||||
gtest.Assert(v, slice[k])
|
||||
return true
|
||||
})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
index := 0
|
||||
array.Iterator(func(k int, v string) bool {
|
||||
index++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(index, 1)
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
index := 0
|
||||
array.IteratorAsc(func(k int, v string) bool {
|
||||
index++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(index, 1)
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
index := 0
|
||||
array.IteratorDesc(func(k int, v string) bool {
|
||||
index++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(index, 1)
|
||||
})
|
||||
}
|
||||
|
||||
@ -587,3 +587,50 @@ func TestSortedArray_Json(t *testing.T) {
|
||||
gtest.AssertIN(user.Scores.PopLeft(), data["Scores"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_Iterator(t *testing.T) {
|
||||
slice := g.Slice{"a", "b", "d", "c"}
|
||||
array := garray.NewSortedArrayFrom(slice, gutil.ComparatorString)
|
||||
gtest.Case(t, func() {
|
||||
array.Iterator(func(k int, v interface{}) bool {
|
||||
gtest.Assert(v, slice[k])
|
||||
return true
|
||||
})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
array.IteratorAsc(func(k int, v interface{}) bool {
|
||||
gtest.Assert(v, slice[k])
|
||||
return true
|
||||
})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
array.IteratorDesc(func(k int, v interface{}) bool {
|
||||
gtest.Assert(v, slice[k])
|
||||
return true
|
||||
})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
index := 0
|
||||
array.Iterator(func(k int, v interface{}) bool {
|
||||
index++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(index, 1)
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
index := 0
|
||||
array.IteratorAsc(func(k int, v interface{}) bool {
|
||||
index++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(index, 1)
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
index := 0
|
||||
array.IteratorDesc(func(k int, v interface{}) bool {
|
||||
index++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(index, 1)
|
||||
})
|
||||
}
|
||||
|
||||
@ -466,3 +466,50 @@ func TestSortedIntArray_Json(t *testing.T) {
|
||||
gtest.Assert(user.Scores, []int{98, 99, 100})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_Iterator(t *testing.T) {
|
||||
slice := g.SliceInt{10, 20, 30, 40}
|
||||
array := garray.NewSortedIntArrayFrom(slice)
|
||||
gtest.Case(t, func() {
|
||||
array.Iterator(func(k int, v int) bool {
|
||||
gtest.Assert(v, slice[k])
|
||||
return true
|
||||
})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
array.IteratorAsc(func(k int, v int) bool {
|
||||
gtest.Assert(v, slice[k])
|
||||
return true
|
||||
})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
array.IteratorDesc(func(k int, v int) bool {
|
||||
gtest.Assert(v, slice[k])
|
||||
return true
|
||||
})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
index := 0
|
||||
array.Iterator(func(k int, v int) bool {
|
||||
index++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(index, 1)
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
index := 0
|
||||
array.IteratorAsc(func(k int, v int) bool {
|
||||
index++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(index, 1)
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
index := 0
|
||||
array.IteratorDesc(func(k int, v int) bool {
|
||||
index++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(index, 1)
|
||||
})
|
||||
}
|
||||
|
||||
@ -476,3 +476,50 @@ func TestSortedStrArray_Json(t *testing.T) {
|
||||
gtest.Assert(user.Scores, []string{"A", "A", "A+"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStrArray_Iterator(t *testing.T) {
|
||||
slice := g.SliceStr{"a", "b", "d", "c"}
|
||||
array := garray.NewSortedStrArrayFrom(slice)
|
||||
gtest.Case(t, func() {
|
||||
array.Iterator(func(k int, v string) bool {
|
||||
gtest.Assert(v, slice[k])
|
||||
return true
|
||||
})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
array.IteratorAsc(func(k int, v string) bool {
|
||||
gtest.Assert(v, slice[k])
|
||||
return true
|
||||
})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
array.IteratorDesc(func(k int, v string) bool {
|
||||
gtest.Assert(v, slice[k])
|
||||
return true
|
||||
})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
index := 0
|
||||
array.Iterator(func(k int, v string) bool {
|
||||
index++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(index, 1)
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
index := 0
|
||||
array.IteratorAsc(func(k int, v string) bool {
|
||||
index++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(index, 1)
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
index := 0
|
||||
array.IteratorDesc(func(k int, v string) bool {
|
||||
index++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(index, 1)
|
||||
})
|
||||
}
|
||||
|
||||
@ -364,6 +364,7 @@ func (l *List) Iterator(f func(e *Element) bool) {
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (l *List) IteratorAsc(f func(e *Element) bool) {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
length := l.list.Len()
|
||||
if length > 0 {
|
||||
for i, e := 0, l.list.Front(); i < length; i, e = i+1, e.Next() {
|
||||
@ -372,13 +373,13 @@ func (l *List) IteratorAsc(f func(e *Element) bool) {
|
||||
}
|
||||
}
|
||||
}
|
||||
l.mu.RUnlock()
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the list in descending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (l *List) IteratorDesc(f func(e *Element) bool) {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
length := l.list.Len()
|
||||
if length > 0 {
|
||||
for i, e := 0, l.list.Back(); i < length; i, e = i+1, e.Prev() {
|
||||
@ -387,7 +388,6 @@ func (l *List) IteratorDesc(f func(e *Element) bool) {
|
||||
}
|
||||
}
|
||||
}
|
||||
l.mu.RUnlock()
|
||||
}
|
||||
|
||||
// Join joins list elements with a string <glue>.
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Bool is a struct for concurrent-safe operation for type bool.
|
||||
type Bool struct {
|
||||
value int32
|
||||
}
|
||||
@ -21,7 +22,7 @@ var (
|
||||
bytesFalse = []byte("false")
|
||||
)
|
||||
|
||||
// NewBool returns a concurrent-safe object for bool type,
|
||||
// NewBool creates and returns a concurrent-safe object for bool type,
|
||||
// with given initial value <value>.
|
||||
func NewBool(value ...bool) *Bool {
|
||||
t := &Bool{}
|
||||
@ -50,7 +51,7 @@ func (v *Bool) Set(value bool) (old bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// Val atomically loads t.valueue.
|
||||
// Val atomically loads and returns t.valueue.
|
||||
func (v *Bool) Val() bool {
|
||||
return atomic.LoadInt32(&v.value) > 0
|
||||
}
|
||||
|
||||
@ -12,11 +12,12 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Byte is a struct for concurrent-safe operation for type byte.
|
||||
type Byte struct {
|
||||
value int32
|
||||
}
|
||||
|
||||
// NewByte returns a concurrent-safe object for byte type,
|
||||
// NewByte creates and returns a concurrent-safe object for byte type,
|
||||
// with given initial value <value>.
|
||||
func NewByte(value ...byte) *Byte {
|
||||
if len(value) > 0 {
|
||||
@ -37,7 +38,7 @@ func (v *Byte) Set(value byte) (old byte) {
|
||||
return byte(atomic.SwapInt32(&v.value, int32(value)))
|
||||
}
|
||||
|
||||
// Val atomically loads t.value.
|
||||
// Val atomically loads and returns t.value.
|
||||
func (v *Byte) Val() byte {
|
||||
return byte(atomic.LoadInt32(&v.value))
|
||||
}
|
||||
|
||||
@ -13,11 +13,12 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Bytes is a struct for concurrent-safe operation for type []byte.
|
||||
type Bytes struct {
|
||||
value atomic.Value
|
||||
}
|
||||
|
||||
// NewBytes returns a concurrent-safe object for []byte type,
|
||||
// NewBytes creates and returns a concurrent-safe object for []byte type,
|
||||
// with given initial value <value>.
|
||||
func NewBytes(value ...[]byte) *Bytes {
|
||||
t := &Bytes{}
|
||||
@ -40,7 +41,7 @@ func (v *Bytes) Set(value []byte) (old []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
// Val atomically loads t.value.
|
||||
// Val atomically loads and returns t.value.
|
||||
func (v *Bytes) Val() []byte {
|
||||
if s := v.value.Load(); s != nil {
|
||||
return s.([]byte)
|
||||
|
||||
@ -14,11 +14,12 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Float32 is a struct for concurrent-safe operation for type float32.
|
||||
type Float32 struct {
|
||||
value uint32
|
||||
}
|
||||
|
||||
// NewFloat32 returns a concurrent-safe object for float32 type,
|
||||
// NewFloat32 creates and returns a concurrent-safe object for float32 type,
|
||||
// with given initial value <value>.
|
||||
func NewFloat32(value ...float32) *Float32 {
|
||||
if len(value) > 0 {
|
||||
@ -39,7 +40,7 @@ func (v *Float32) Set(value float32) (old float32) {
|
||||
return math.Float32frombits(atomic.SwapUint32(&v.value, math.Float32bits(value)))
|
||||
}
|
||||
|
||||
// Val atomically loads t.value.
|
||||
// Val atomically loads and returns t.value.
|
||||
func (v *Float32) Val() float32 {
|
||||
return math.Float32frombits(atomic.LoadUint32(&v.value))
|
||||
}
|
||||
|
||||
@ -14,11 +14,12 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Float64 is a struct for concurrent-safe operation for type float64.
|
||||
type Float64 struct {
|
||||
value uint64
|
||||
}
|
||||
|
||||
// NewFloat64 returns a concurrent-safe object for float64 type,
|
||||
// NewFloat64 creates and returns a concurrent-safe object for float64 type,
|
||||
// with given initial value <value>.
|
||||
func NewFloat64(value ...float64) *Float64 {
|
||||
if len(value) > 0 {
|
||||
@ -39,7 +40,7 @@ func (v *Float64) Set(value float64) (old float64) {
|
||||
return math.Float64frombits(atomic.SwapUint64(&v.value, math.Float64bits(value)))
|
||||
}
|
||||
|
||||
// Val atomically loads t.value.
|
||||
// Val atomically loads and returns t.value.
|
||||
func (v *Float64) Val() float64 {
|
||||
return math.Float64frombits(atomic.LoadUint64(&v.value))
|
||||
}
|
||||
|
||||
@ -12,11 +12,12 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Int is a struct for concurrent-safe operation for type int.
|
||||
type Int struct {
|
||||
value int64
|
||||
}
|
||||
|
||||
// NewInt returns a concurrent-safe object for int type,
|
||||
// NewInt creates and returns a concurrent-safe object for int type,
|
||||
// with given initial value <value>.
|
||||
func NewInt(value ...int) *Int {
|
||||
if len(value) > 0 {
|
||||
@ -37,7 +38,7 @@ func (v *Int) Set(value int) (old int) {
|
||||
return int(atomic.SwapInt64(&v.value, int64(value)))
|
||||
}
|
||||
|
||||
// Val atomically loads t.value.
|
||||
// Val atomically loads and returns t.value.
|
||||
func (v *Int) Val() int {
|
||||
return int(atomic.LoadInt64(&v.value))
|
||||
}
|
||||
|
||||
@ -12,11 +12,12 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Int32 is a struct for concurrent-safe operation for type int32.
|
||||
type Int32 struct {
|
||||
value int32
|
||||
}
|
||||
|
||||
// NewInt32 returns a concurrent-safe object for int32 type,
|
||||
// NewInt32 creates and returns a concurrent-safe object for int32 type,
|
||||
// with given initial value <value>.
|
||||
func NewInt32(value ...int32) *Int32 {
|
||||
if len(value) > 0 {
|
||||
@ -37,7 +38,7 @@ func (v *Int32) Set(value int32) (old int32) {
|
||||
return atomic.SwapInt32(&v.value, value)
|
||||
}
|
||||
|
||||
// Val atomically loads t.value.
|
||||
// Val atomically loads and returns t.value.
|
||||
func (v *Int32) Val() int32 {
|
||||
return atomic.LoadInt32(&v.value)
|
||||
}
|
||||
|
||||
@ -12,11 +12,12 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Int64 is a struct for concurrent-safe operation for type int64.
|
||||
type Int64 struct {
|
||||
value int64
|
||||
}
|
||||
|
||||
// NewInt64 returns a concurrent-safe object for int64 type,
|
||||
// NewInt64 creates and returns a concurrent-safe object for int64 type,
|
||||
// with given initial value <value>.
|
||||
func NewInt64(value ...int64) *Int64 {
|
||||
if len(value) > 0 {
|
||||
@ -37,7 +38,7 @@ func (v *Int64) Set(value int64) (old int64) {
|
||||
return atomic.SwapInt64(&v.value, value)
|
||||
}
|
||||
|
||||
// Val atomically loads t.value.
|
||||
// Val atomically loads and returns t.value.
|
||||
func (v *Int64) Val() int64 {
|
||||
return atomic.LoadInt64(&v.value)
|
||||
}
|
||||
|
||||
@ -12,11 +12,12 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Interface is a struct for concurrent-safe operation for type interface{}.
|
||||
type Interface struct {
|
||||
value atomic.Value
|
||||
}
|
||||
|
||||
// NewInterface returns a concurrent-safe object for interface{} type,
|
||||
// NewInterface creates and returns a concurrent-safe object for interface{} type,
|
||||
// with given initial value <value>.
|
||||
func NewInterface(value ...interface{}) *Interface {
|
||||
t := &Interface{}
|
||||
@ -39,7 +40,7 @@ func (v *Interface) Set(value interface{}) (old interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
// Val atomically loads t.value.
|
||||
// Val atomically loads and returns t.value.
|
||||
func (v *Interface) Val() interface{} {
|
||||
return v.value.Load()
|
||||
}
|
||||
|
||||
@ -12,11 +12,12 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// String is a struct for concurrent-safe operation for type string.
|
||||
type String struct {
|
||||
value atomic.Value
|
||||
}
|
||||
|
||||
// NewString returns a concurrent-safe object for string type,
|
||||
// NewString creates and returns a concurrent-safe object for string type,
|
||||
// with given initial value <value>.
|
||||
func NewString(value ...string) *String {
|
||||
t := &String{}
|
||||
@ -38,7 +39,7 @@ func (v *String) Set(value string) (old string) {
|
||||
return
|
||||
}
|
||||
|
||||
// Val atomically loads t.value.
|
||||
// Val atomically loads and returns t.value.
|
||||
func (v *String) Val() string {
|
||||
s := v.value.Load()
|
||||
if s != nil {
|
||||
|
||||
@ -12,11 +12,12 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Uint is a struct for concurrent-safe operation for type uint.
|
||||
type Uint struct {
|
||||
value uint64
|
||||
}
|
||||
|
||||
// NewUint returns a concurrent-safe object for uint type,
|
||||
// NewUint creates and returns a concurrent-safe object for uint type,
|
||||
// with given initial value <value>.
|
||||
func NewUint(value ...uint) *Uint {
|
||||
if len(value) > 0 {
|
||||
@ -37,7 +38,7 @@ func (v *Uint) Set(value uint) (old uint) {
|
||||
return uint(atomic.SwapUint64(&v.value, uint64(value)))
|
||||
}
|
||||
|
||||
// Val atomically loads t.value.
|
||||
// Val atomically loads and returns t.value.
|
||||
func (v *Uint) Val() uint {
|
||||
return uint(atomic.LoadUint64(&v.value))
|
||||
}
|
||||
|
||||
@ -12,11 +12,12 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Uint32 is a struct for concurrent-safe operation for type uint32.
|
||||
type Uint32 struct {
|
||||
value uint32
|
||||
}
|
||||
|
||||
// NewUint32 returns a concurrent-safe object for uint32 type,
|
||||
// NewUint32 creates and returns a concurrent-safe object for uint32 type,
|
||||
// with given initial value <value>.
|
||||
func NewUint32(value ...uint32) *Uint32 {
|
||||
if len(value) > 0 {
|
||||
@ -37,7 +38,7 @@ func (v *Uint32) Set(value uint32) (old uint32) {
|
||||
return atomic.SwapUint32(&v.value, value)
|
||||
}
|
||||
|
||||
// Val atomically loads t.value.
|
||||
// Val atomically loads and returns t.value.
|
||||
func (v *Uint32) Val() uint32 {
|
||||
return atomic.LoadUint32(&v.value)
|
||||
}
|
||||
|
||||
@ -12,11 +12,12 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Uint64 is a struct for concurrent-safe operation for type uint64.
|
||||
type Uint64 struct {
|
||||
value uint64
|
||||
}
|
||||
|
||||
// NewUint64 returns a concurrent-safe object for uint64 type,
|
||||
// NewUint64 creates and returns a concurrent-safe object for uint64 type,
|
||||
// with given initial value <value>.
|
||||
func NewUint64(value ...uint64) *Uint64 {
|
||||
if len(value) > 0 {
|
||||
@ -37,7 +38,7 @@ func (v *Uint64) Set(value uint64) (old uint64) {
|
||||
return atomic.SwapUint64(&v.value, value)
|
||||
}
|
||||
|
||||
// Val atomically loads t.value.
|
||||
// Val atomically loads and returns t.value.
|
||||
func (v *Uint64) Val() uint64 {
|
||||
return atomic.LoadUint64(&v.value)
|
||||
}
|
||||
|
||||
@ -22,17 +22,18 @@ import (
|
||||
"github.com/gogf/gf/util/grand"
|
||||
)
|
||||
|
||||
// 数据库操作接口
|
||||
// DB is the interface for ORM operations.
|
||||
type DB interface {
|
||||
// 建立数据库连接方法(开发者一般不需要直接调用)
|
||||
// Open creates a raw connection object for database with given node configuration.
|
||||
// Note that it is not recommended using the this function manually.
|
||||
Open(config *ConfigNode) (*sql.DB, error)
|
||||
|
||||
// SQL操作方法 API
|
||||
// Query APIs.
|
||||
Query(query string, args ...interface{}) (*sql.Rows, error)
|
||||
Exec(sql string, args ...interface{}) (sql.Result, error)
|
||||
Prepare(sql string, execOnMaster ...bool) (*sql.Stmt, error)
|
||||
|
||||
// 内部实现API的方法(不同数据库可覆盖这些方法实现自定义的操作)
|
||||
// Internal APIs for CURD, which can be overwrote for custom CURD implements.
|
||||
doQuery(link dbLink, query string, args ...interface{}) (rows *sql.Rows, err error)
|
||||
doGetAll(link dbLink, query string, args ...interface{}) (result Result, err error)
|
||||
doExec(link dbLink, query string, args ...interface{}) (result sql.Result, err error)
|
||||
@ -42,7 +43,7 @@ type DB interface {
|
||||
doUpdate(link dbLink, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error)
|
||||
doDelete(link dbLink, table string, condition string, args ...interface{}) (result sql.Result, err error)
|
||||
|
||||
// 数据库查询
|
||||
// Query APIs for convenience purpose.
|
||||
GetAll(query string, args ...interface{}) (Result, error)
|
||||
GetOne(query string, args ...interface{}) (Record, error)
|
||||
GetValue(query string, args ...interface{}) (Value, error)
|
||||
@ -51,36 +52,33 @@ type DB interface {
|
||||
GetStructs(objPointerSlice interface{}, query string, args ...interface{}) error
|
||||
GetScan(objPointer interface{}, query string, args ...interface{}) error
|
||||
|
||||
// 创建底层数据库master/slave链接对象
|
||||
// Master/Slave support.
|
||||
Master() (*sql.DB, error)
|
||||
Slave() (*sql.DB, error)
|
||||
|
||||
// Ping
|
||||
// Ping.
|
||||
PingMaster() error
|
||||
PingSlave() error
|
||||
|
||||
// 开启事务操作
|
||||
// Transaction.
|
||||
Begin() (*TX, error)
|
||||
|
||||
// 数据表插入/更新/保存操作
|
||||
Insert(table string, data interface{}, batch ...int) (sql.Result, error)
|
||||
Replace(table string, data interface{}, batch ...int) (sql.Result, error)
|
||||
Save(table string, data interface{}, batch ...int) (sql.Result, error)
|
||||
|
||||
// 数据表插入/更新/保存操作(批量)
|
||||
BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error)
|
||||
BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error)
|
||||
BatchSave(table string, list interface{}, batch ...int) (sql.Result, error)
|
||||
|
||||
// 数据修改/删除
|
||||
Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error)
|
||||
Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error)
|
||||
|
||||
// 创建链式操作对象
|
||||
// Create model.
|
||||
From(tables string) *Model
|
||||
Table(tables string) *Model
|
||||
|
||||
// 设置管理
|
||||
// Configuration methods.
|
||||
SetDebug(debug bool)
|
||||
SetSchema(schema string)
|
||||
SetLogger(logger *glog.Logger)
|
||||
@ -91,13 +89,14 @@ type DB interface {
|
||||
Tables() (tables []string, err error)
|
||||
TableFields(table string) (map[string]*TableField, error)
|
||||
|
||||
// 内部方法接口
|
||||
// Internal methods.
|
||||
getCache() *gcache.Cache
|
||||
getChars() (charLeft string, charRight string)
|
||||
getDebug() bool
|
||||
getPrefix() string
|
||||
quoteWord(s string) string
|
||||
quoteString(s string) string
|
||||
handleTableName(table string) string
|
||||
doSetSchema(sqlDb *sql.DB, schema string) error
|
||||
filterFields(table string, data map[string]interface{}) map[string]interface{}
|
||||
convertValue(fieldValue []byte, fieldType string) interface{}
|
||||
|
||||
@ -31,17 +31,6 @@ var (
|
||||
lastOperatorReg = regexp.MustCompile(`[<>=]+\s*$`)
|
||||
)
|
||||
|
||||
// 打印SQL对象(仅在debug=true时有效)
|
||||
func (bs *dbBase) printSql(v *Sql) {
|
||||
s := fmt.Sprintf("[%d ms] %s", v.End-v.Start, v.Format)
|
||||
if v.Error != nil {
|
||||
s += "\nError: " + v.Error.Error()
|
||||
bs.logger.StackWithFilter(gPATH_FILTER_KEY).Error(s)
|
||||
} else {
|
||||
bs.logger.StackWithFilter(gPATH_FILTER_KEY).Debug(s)
|
||||
}
|
||||
}
|
||||
|
||||
// 数据库sql查询操作,主要执行查询
|
||||
func (bs *dbBase) Query(query string, args ...interface{}) (rows *sql.Rows, err error) {
|
||||
link, err := bs.db.Slave()
|
||||
@ -56,9 +45,9 @@ func (bs *dbBase) doQuery(link dbLink, query string, args ...interface{}) (rows
|
||||
query, args = formatQuery(query, args)
|
||||
query = bs.db.handleSqlBeforeExec(query)
|
||||
if bs.db.getDebug() {
|
||||
mTime1 := gtime.Millisecond()
|
||||
mTime1 := gtime.TimestampMicro()
|
||||
rows, err = link.Query(query, args...)
|
||||
mTime2 := gtime.Millisecond()
|
||||
mTime2 := gtime.TimestampMicro()
|
||||
s := &Sql{
|
||||
Sql: query,
|
||||
Args: args,
|
||||
@ -302,7 +291,7 @@ func (bs *dbBase) doInsert(link dbLink, table string, data interface{}, option i
|
||||
var values []string
|
||||
var params []interface{}
|
||||
var dataMap Map
|
||||
table = bs.db.quoteWord(table)
|
||||
table = bs.db.handleTableName(table)
|
||||
// 使用反射判断data数据类型,如果为slice类型,那么自动转为批量操作
|
||||
rv := reflect.ValueOf(data)
|
||||
kind := rv.Kind()
|
||||
@ -371,7 +360,7 @@ func (bs *dbBase) BatchSave(table string, list interface{}, batch ...int) (sql.R
|
||||
func (bs *dbBase) doBatchInsert(link dbLink, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
var keys, values []string
|
||||
var params []interface{}
|
||||
table = bs.db.quoteWord(table)
|
||||
table = bs.db.handleTableName(table)
|
||||
listMap := (List)(nil)
|
||||
switch v := list.(type) {
|
||||
case Result:
|
||||
@ -491,7 +480,7 @@ func (bs *dbBase) Update(table string, data interface{}, condition interface{},
|
||||
// CURD操作:数据更新,统一采用sql预处理。
|
||||
// data参数支持string/map/struct/*struct类型类型。
|
||||
func (bs *dbBase) doUpdate(link dbLink, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) {
|
||||
table = bs.db.quoteWord(table)
|
||||
table = bs.db.handleTableName(table)
|
||||
updates := ""
|
||||
// 使用反射进行类型判断
|
||||
rv := reflect.ValueOf(data)
|
||||
@ -543,7 +532,7 @@ func (bs *dbBase) doDelete(link dbLink, table string, condition string, args ...
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
table = bs.db.quoteWord(table)
|
||||
table = bs.db.handleTableName(table)
|
||||
return bs.db.doExec(link, fmt.Sprintf("DELETE FROM %s%s", table, condition), args...)
|
||||
}
|
||||
|
||||
@ -605,6 +594,17 @@ func (bs *dbBase) rowsToResult(rows *sql.Rows) (Result, error) {
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// handleTableName adds prefix string and quote chars for the table. It handles table string like:
|
||||
// "user", "user u", "user,user_detail", "user u, user_detail ut", "user as u, user_detail as ut".
|
||||
//
|
||||
// Note that, this will automatically checks the table prefix whether already added, if true it does
|
||||
// nothing to the table name, or else adds the prefix to the table name.
|
||||
func (bs *dbBase) handleTableName(table string) string {
|
||||
charLeft, charRight := bs.db.getChars()
|
||||
prefix := bs.db.getPrefix()
|
||||
return doHandleTableName(table, prefix, charLeft, charRight)
|
||||
}
|
||||
|
||||
// quoteWord checks given string <s> a word, if true quotes it with security chars of the database
|
||||
// and returns the quoted string; or else return <s> without any change.
|
||||
func (bs *dbBase) quoteWord(s string) string {
|
||||
@ -619,6 +619,17 @@ func (bs *dbBase) quoteString(s string) string {
|
||||
return doQuoteString(s, charLeft, charRight)
|
||||
}
|
||||
|
||||
// 打印SQL对象(仅在debug=true时有效)
|
||||
func (bs *dbBase) printSql(v *Sql) {
|
||||
s := fmt.Sprintf("[%d ms] %s", v.End-v.Start, v.Format)
|
||||
if v.Error != nil {
|
||||
s += "\nError: " + v.Error.Error()
|
||||
bs.logger.StackWithFilter(gPATH_FILTER_KEY).Error(s)
|
||||
} else {
|
||||
bs.logger.StackWithFilter(gPATH_FILTER_KEY).Debug(s)
|
||||
}
|
||||
}
|
||||
|
||||
// 动态切换数据库
|
||||
func (bs *dbBase) doSetSchema(sqlDb *sql.DB, schema string) error {
|
||||
_, err := sqlDb.Exec("USE " + schema)
|
||||
|
||||
@ -51,8 +51,31 @@ var (
|
||||
quoteWordReg = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`)
|
||||
)
|
||||
|
||||
// handleTableName adds prefix string and quote chars for the table. It handles table string like:
|
||||
// "user", "user u", "user,user_detail", "user u, user_detail ut", "user as u, user_detail as ut".
|
||||
//
|
||||
// Note that, this will automatically checks the table prefix whether already added, if true it does
|
||||
// nothing to the table name, or else adds the prefix to the table name.
|
||||
func doHandleTableName(table, prefix, charLeft, charRight string) string {
|
||||
array1 := gstr.SplitAndTrim(table, ",")
|
||||
for k1, v1 := range array1 {
|
||||
array2 := gstr.SplitAndTrim(v1, " ")
|
||||
// Trim the security chars.
|
||||
array2[0] = gstr.TrimLeftStr(array2[0], charLeft)
|
||||
array2[0] = gstr.TrimRightStr(array2[0], charRight)
|
||||
// If the table name already has the prefix, skips the prefix adding.
|
||||
if len(array2[0]) <= len(prefix) || array2[0][:len(prefix)] != prefix {
|
||||
array2[0] = prefix + array2[0]
|
||||
}
|
||||
// Add the security chars.
|
||||
array2[0] = doQuoteWord(array2[0], charLeft, charRight)
|
||||
array1[k1] = gstr.Join(array2, " ")
|
||||
}
|
||||
return gstr.Join(array1, ",")
|
||||
}
|
||||
|
||||
// doQuoteWord checks given string <s> a word, if true quotes it with <charLeft> and <charRight>
|
||||
// and returns the quoted string; or else return <s> without any change.
|
||||
// and returns the quoted string; or else returns <s> without any change.
|
||||
func doQuoteWord(s, charLeft, charRight string) string {
|
||||
if quoteWordReg.MatchString(s) && !gstr.ContainsAny(s, charLeft+charRight) {
|
||||
return charLeft + s + charRight
|
||||
@ -78,23 +101,6 @@ func doQuoteString(s, charLeft, charRight string) string {
|
||||
return gstr.Join(array1, ",")
|
||||
}
|
||||
|
||||
// addTablePrefix adds prefix string to the table. It handles table string like:
|
||||
// "user", "user u", "user,user_detail", "user u, user_detail ut", "user as u, user_detail as ut".
|
||||
//
|
||||
// Note that, this should be used before any quoting function calls.
|
||||
func addTablePrefix(table, prefix string) string {
|
||||
if prefix == "" {
|
||||
return table
|
||||
}
|
||||
array1 := gstr.SplitAndTrim(table, ",")
|
||||
for k1, v1 := range array1 {
|
||||
array2 := gstr.SplitAndTrim(v1, " ")
|
||||
array2[0] = prefix + array2[0]
|
||||
array1[k1] = gstr.Join(array2, " ")
|
||||
}
|
||||
return gstr.Join(array1, ",")
|
||||
}
|
||||
|
||||
// GetWhereConditionOfStruct returns the where condition sql and arguments by given struct pointer.
|
||||
// This function automatically retrieves primary or unique field and its attribute value as condition.
|
||||
func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interface{}) {
|
||||
|
||||
@ -67,8 +67,7 @@ const (
|
||||
// The parameter <tables> can be more than one table names, like :
|
||||
// "user", "user u", "user, user_detail", "user u, user_detail ud"
|
||||
func (bs *dbBase) Table(table string) *Model {
|
||||
table = addTablePrefix(table, bs.db.getPrefix())
|
||||
table = bs.db.quoteString(table)
|
||||
table = bs.db.handleTableName(table)
|
||||
return &Model{
|
||||
db: bs.db,
|
||||
tablesInit: table,
|
||||
@ -81,8 +80,15 @@ func (bs *dbBase) Table(table string) *Model {
|
||||
}
|
||||
}
|
||||
|
||||
// Model is alias of dbBase.Table.
|
||||
// See dbBase.Table.
|
||||
func (bs *dbBase) Model(tables string) *Model {
|
||||
return bs.db.Table(tables)
|
||||
}
|
||||
|
||||
// From is alias of dbBase.Table.
|
||||
// See dbBase.Table.
|
||||
// Deprecated.
|
||||
func (bs *dbBase) From(tables string) *Model {
|
||||
return bs.db.Table(tables)
|
||||
}
|
||||
@ -90,8 +96,7 @@ func (bs *dbBase) From(tables string) *Model {
|
||||
// Table acts like dbBase.Table except it operates on transaction.
|
||||
// See dbBase.Table.
|
||||
func (tx *TX) Table(table string) *Model {
|
||||
table = addTablePrefix(table, tx.db.getPrefix())
|
||||
table = tx.db.quoteString(table)
|
||||
table = tx.db.handleTableName(table)
|
||||
return &Model{
|
||||
db: tx.db,
|
||||
tx: tx,
|
||||
@ -105,8 +110,15 @@ func (tx *TX) Table(table string) *Model {
|
||||
}
|
||||
}
|
||||
|
||||
// Model is alias of tx.Table.
|
||||
// See tx.Table.
|
||||
func (tx *TX) Model(tables string) *Model {
|
||||
return tx.Table(tables)
|
||||
}
|
||||
|
||||
// From is alias of tx.Table.
|
||||
// See tx.Table.
|
||||
// Deprecated.
|
||||
func (tx *TX) From(tables string) *Model {
|
||||
return tx.Table(tables)
|
||||
}
|
||||
@ -186,27 +198,21 @@ func (m *Model) getModel() *Model {
|
||||
// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
|
||||
func (m *Model) LeftJoin(table string, on string) *Model {
|
||||
model := m.getModel()
|
||||
table = addTablePrefix(table, m.db.getPrefix())
|
||||
table = m.db.quoteString(table)
|
||||
model.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", table, on)
|
||||
model.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", m.db.handleTableName(table), on)
|
||||
return model
|
||||
}
|
||||
|
||||
// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
|
||||
func (m *Model) RightJoin(table string, on string) *Model {
|
||||
model := m.getModel()
|
||||
table = addTablePrefix(table, m.db.getPrefix())
|
||||
table = m.db.quoteString(table)
|
||||
model.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", table, on)
|
||||
model.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", m.db.handleTableName(table), on)
|
||||
return model
|
||||
}
|
||||
|
||||
// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
|
||||
func (m *Model) InnerJoin(table string, on string) *Model {
|
||||
model := m.getModel()
|
||||
table = addTablePrefix(table, m.db.getPrefix())
|
||||
table = m.db.quoteString(table)
|
||||
model.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", table, on)
|
||||
model.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", m.db.handleTableName(table), on)
|
||||
return model
|
||||
}
|
||||
|
||||
@ -881,6 +887,15 @@ func (m *Model) FindCount(where ...interface{}) (int, error) {
|
||||
return m.Count()
|
||||
}
|
||||
|
||||
// FindScan retrieves and returns the record/records by Model.WherePri and Model.Scan.
|
||||
// Also see Model.WherePri and Model.Scan.
|
||||
func (m *Model) FindScan(pointer interface{}, where ...interface{}) error {
|
||||
if len(where) > 0 {
|
||||
return m.WherePri(where[0], where[1:]...).Scan(pointer)
|
||||
}
|
||||
return m.Scan(pointer)
|
||||
}
|
||||
|
||||
// Chunk iterates the table with given size and callback function.
|
||||
func (m *Model) Chunk(limit int, callback func(result Result, err error) bool) {
|
||||
page := m.start
|
||||
|
||||
@ -49,29 +49,29 @@ func Test_Func_addTablePrefix(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
prefix := ""
|
||||
array := map[string]string{
|
||||
"user": "user",
|
||||
"user u": "user u",
|
||||
"user as u": "user as u",
|
||||
"user,user_detail": "user,user_detail",
|
||||
"user u, user_detail ut": "user u, user_detail ut",
|
||||
"user as u, user_detail as ut": "user as u, user_detail as ut",
|
||||
"user": "`user`",
|
||||
"user u": "`user` u",
|
||||
"user as u": "`user` as u",
|
||||
"user,user_detail": "`user`,`user_detail`",
|
||||
"user u, user_detail ut": "`user` u,`user_detail` ut",
|
||||
"user as u, user_detail as ut": "`user` as u,`user_detail` as ut",
|
||||
}
|
||||
for k, v := range array {
|
||||
gtest.Assert(addTablePrefix(k, prefix), v)
|
||||
gtest.Assert(doHandleTableName(k, prefix, "`", "`"), v)
|
||||
}
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
prefix := "gf_"
|
||||
array := map[string]string{
|
||||
"user": "gf_user",
|
||||
"user u": "gf_user u",
|
||||
"user as u": "gf_user as u",
|
||||
"user,user_detail": "gf_user,gf_user_detail",
|
||||
"user u, user_detail ut": "gf_user u,gf_user_detail ut",
|
||||
"user as u, user_detail as ut": "gf_user as u,gf_user_detail as ut",
|
||||
"user": "`gf_user`",
|
||||
"user u": "`gf_user` u",
|
||||
"user as u": "`gf_user` as u",
|
||||
"user,user_detail": "`gf_user`,`gf_user_detail`",
|
||||
"user u, user_detail ut": "`gf_user` u,`gf_user_detail` ut",
|
||||
"user as u, user_detail as ut": "`gf_user` as u,`gf_user_detail` as ut",
|
||||
}
|
||||
for k, v := range array {
|
||||
gtest.Assert(addTablePrefix(k, prefix), v)
|
||||
gtest.Assert(doHandleTableName(k, prefix, "`", "`"), v)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ package gdb_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -251,17 +252,20 @@ func Test_DB_BatchInsert(t *testing.T) {
|
||||
gtest.Assert(n, 1)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func Test_DB_BatchInsert_Struct(t *testing.T) {
|
||||
// batch insert struct
|
||||
gtest.Case(t, func() {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
type User struct {
|
||||
Id int `gconv:"id"`
|
||||
Passport string `gconv:"passport"`
|
||||
Password string `gconv:"password"`
|
||||
NickName string `gconv:"nickname"`
|
||||
CreateTime *gtime.Time `gconv:"create_time"`
|
||||
Id int `c:"id"`
|
||||
Passport string `c:"passport"`
|
||||
Password string `c:"password"`
|
||||
NickName string `c:"nickname"`
|
||||
CreateTime *gtime.Time `c:"create_time"`
|
||||
}
|
||||
user := &User{
|
||||
Id: 1,
|
||||
@ -275,7 +279,6 @@ func Test_DB_BatchInsert(t *testing.T) {
|
||||
n, _ := result.RowsAffected()
|
||||
gtest.Assert(n, 1)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func Test_DB_Save(t *testing.T) {
|
||||
@ -1052,6 +1055,109 @@ func Test_DB_TableField(t *testing.T) {
|
||||
gtest.Assert(result[0], data)
|
||||
}
|
||||
|
||||
func Test_DB_Prefix(t *testing.T) {
|
||||
db := dbPrefix
|
||||
name := fmt.Sprintf(`%s_%d`, TABLE, gtime.TimestampNano())
|
||||
table := PREFIX1 + name
|
||||
createTableWithDb(db, table)
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.Case(t, func() {
|
||||
id := 10000
|
||||
result, err := db.Insert(name, g.Map{
|
||||
"id": id,
|
||||
"passport": fmt.Sprintf(`user_%d`, id),
|
||||
"password": fmt.Sprintf(`pass_%d`, id),
|
||||
"nickname": fmt.Sprintf(`name_%d`, id),
|
||||
"create_time": gtime.NewFromStr("2018-10-24 10:00:00").String(),
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
n, e := result.RowsAffected()
|
||||
gtest.Assert(e, nil)
|
||||
gtest.Assert(n, 1)
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
id := 10000
|
||||
result, err := db.Replace(name, g.Map{
|
||||
"id": id,
|
||||
"passport": fmt.Sprintf(`user_%d`, id),
|
||||
"password": fmt.Sprintf(`pass_%d`, id),
|
||||
"nickname": fmt.Sprintf(`name_%d`, id),
|
||||
"create_time": gtime.NewFromStr("2018-10-24 10:00:01").String(),
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
n, e := result.RowsAffected()
|
||||
gtest.Assert(e, nil)
|
||||
gtest.Assert(n, 2)
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
id := 10000
|
||||
result, err := db.Save(name, g.Map{
|
||||
"id": id,
|
||||
"passport": fmt.Sprintf(`user_%d`, id),
|
||||
"password": fmt.Sprintf(`pass_%d`, id),
|
||||
"nickname": fmt.Sprintf(`name_%d`, id),
|
||||
"create_time": gtime.NewFromStr("2018-10-24 10:00:02").String(),
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
n, e := result.RowsAffected()
|
||||
gtest.Assert(e, nil)
|
||||
gtest.Assert(n, 2)
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
id := 10000
|
||||
result, err := db.Update(name, g.Map{
|
||||
"id": id,
|
||||
"passport": fmt.Sprintf(`user_%d`, id),
|
||||
"password": fmt.Sprintf(`pass_%d`, id),
|
||||
"nickname": fmt.Sprintf(`name_%d`, id),
|
||||
"create_time": gtime.NewFromStr("2018-10-24 10:00:03").String(),
|
||||
}, "id=?", id)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
n, e := result.RowsAffected()
|
||||
gtest.Assert(e, nil)
|
||||
gtest.Assert(n, 1)
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
id := 10000
|
||||
result, err := db.Delete(name, "id=?", id)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
n, e := result.RowsAffected()
|
||||
gtest.Assert(e, nil)
|
||||
gtest.Assert(n, 1)
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
array := garray.New(true)
|
||||
for i := 1; i <= INIT_DATA_SIZE; i++ {
|
||||
array.Append(g.Map{
|
||||
"id": i,
|
||||
"passport": fmt.Sprintf(`user_%d`, i),
|
||||
"password": fmt.Sprintf(`pass_%d`, i),
|
||||
"nickname": fmt.Sprintf(`name_%d`, i),
|
||||
"create_time": gtime.NewFromStr("2018-10-24 10:00:00").String(),
|
||||
})
|
||||
}
|
||||
|
||||
result, err := db.BatchInsert(name, array.Slice())
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
n, e := result.RowsAffected()
|
||||
gtest.Assert(e, nil)
|
||||
gtest.Assert(n, INIT_DATA_SIZE)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func Test_Model_InnerJoin(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
table1 := createInitTable("user1")
|
||||
|
||||
@ -667,10 +667,10 @@ func Test_TX_GetScan(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_TX_Delete(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.Case(t, func() {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
gtest.Error(err)
|
||||
@ -688,4 +688,30 @@ func Test_TX_Delete(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
if _, err := tx.Delete(table, nil); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
if n, err := tx.Table(table).Count(); err != nil {
|
||||
gtest.Error(err)
|
||||
} else {
|
||||
gtest.Assert(n, 0)
|
||||
}
|
||||
if err := tx.Rollback(); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
if n, err := db.Table(table).Count(); err != nil {
|
||||
gtest.Error(err)
|
||||
} else {
|
||||
gtest.Assert(n, INIT_DATA_SIZE)
|
||||
gtest.AssertNE(n, 0)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
package gxml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/clbanning/mxj"
|
||||
@ -16,7 +15,7 @@ import (
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
)
|
||||
|
||||
// 将XML内容解析为map变量
|
||||
// Decode parses <content> into and returns as map.
|
||||
func Decode(content []byte) (map[string]interface{}, error) {
|
||||
res, err := convert(content)
|
||||
if err != nil {
|
||||
@ -25,23 +24,42 @@ func Decode(content []byte) (map[string]interface{}, error) {
|
||||
return mxj.NewMapXml(res)
|
||||
}
|
||||
|
||||
// 将map变量解析为XML格式内容
|
||||
func Encode(v map[string]interface{}, rootTag ...string) ([]byte, error) {
|
||||
return mxj.Map(v).Xml(rootTag...)
|
||||
// DecodeWithoutRoot parses <content> into a map, and returns the map without root level.
|
||||
func DecodeWithoutRoot(content []byte) (map[string]interface{}, error) {
|
||||
res, err := convert(content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m, err := mxj.NewMapXml(res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range m {
|
||||
if r, ok := v.(map[string]interface{}); ok {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func EncodeWithIndent(v map[string]interface{}, rootTag ...string) ([]byte, error) {
|
||||
return mxj.Map(v).XmlIndent("", "\t", rootTag...)
|
||||
// Encode encodes map <m> to a XML format content as bytes.
|
||||
// The optional parameter <rootTag> is used to specify the XML root tag.
|
||||
func Encode(m map[string]interface{}, rootTag ...string) ([]byte, error) {
|
||||
return mxj.Map(m).Xml(rootTag...)
|
||||
}
|
||||
|
||||
// XML格式内容直接转换为JSON格式内容
|
||||
// Encode encodes map <m> to a XML format content as bytes with indent.
|
||||
// The optional parameter <rootTag> is used to specify the XML root tag.
|
||||
func EncodeWithIndent(m map[string]interface{}, rootTag ...string) ([]byte, error) {
|
||||
return mxj.Map(m).XmlIndent("", "\t", rootTag...)
|
||||
}
|
||||
|
||||
// ToJson converts <content> as XML format into JSON format bytes.
|
||||
func ToJson(content []byte) ([]byte, error) {
|
||||
res, err := convert(content)
|
||||
if err != nil {
|
||||
fmt.Println("convert error. ", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mv, err := mxj.NewMapXml(res)
|
||||
if err == nil {
|
||||
return mv.Json()
|
||||
@ -50,7 +68,7 @@ func ToJson(content []byte) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// XML字符集预处理
|
||||
// convert converts the encoding of given XML content from XML root tag into UTF-8 encoding content.
|
||||
func convert(xml []byte) (res []byte, err error) {
|
||||
patten := `<\?xml.*encoding\s*=\s*['|"](.*?)['|"].*\?>`
|
||||
matchStr, err := gregex.MatchString(patten, string(xml))
|
||||
|
||||
@ -80,7 +80,7 @@ func Test_XmlToJson(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Decode(t *testing.T) {
|
||||
func Test_Decode1(t *testing.T) {
|
||||
for _, v := range testData {
|
||||
srcXml, dstXml := buildXml(v.otherEncoding, v.utf8)
|
||||
if len(srcXml) == 0 && len(dstXml) == 0 {
|
||||
@ -106,6 +106,32 @@ func Test_Decode(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Decode2(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
content := `
|
||||
<?xml version="1.0" encoding="UTF-8"?><doc><username>johngcn</username><password1>123456</password1><password2>123456</password2></doc>
|
||||
`
|
||||
m, err := gxml.Decode([]byte(content))
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(m["doc"].(map[string]interface{})["username"], "johngcn")
|
||||
gtest.Assert(m["doc"].(map[string]interface{})["password1"], "123456")
|
||||
gtest.Assert(m["doc"].(map[string]interface{})["password2"], "123456")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DecodeWitoutRoot(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
content := `
|
||||
<?xml version="1.0" encoding="UTF-8"?><doc><username>johngcn</username><password1>123456</password1><password2>123456</password2></doc>
|
||||
`
|
||||
m, err := gxml.DecodeWithoutRoot([]byte(content))
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(m["username"], "johngcn")
|
||||
gtest.Assert(m["password1"], "123456")
|
||||
gtest.Assert(m["password2"], "123456")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Encode(t *testing.T) {
|
||||
m := make(map[string]interface{})
|
||||
v := map[string]interface{}{
|
||||
|
||||
@ -131,8 +131,8 @@ func formatSubStack(st stack, buffer *bytes.Buffer) {
|
||||
if strings.Contains(file, gFILTER_KEY) {
|
||||
continue
|
||||
}
|
||||
// Avoid GF stacks if not in GF development.
|
||||
if !intlog.IsInGFDevelop() {
|
||||
// Avoid GF stacks if not in GF development.
|
||||
if strings.Contains(file, "github.com/gogf/gf/") {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -12,7 +12,9 @@ import (
|
||||
"github.com/fatih/structs"
|
||||
)
|
||||
|
||||
// MapField retrieves struct field as map[name/tag]*Field from <pointer>, and returns it.
|
||||
// MapField retrieves struct field as map[name/tag]*Field from <pointer>, and returns the map.
|
||||
//
|
||||
// The parameter <priority> specifies the priority tag array for retrieving from high to low.
|
||||
//
|
||||
// The parameter <recursive> specifies whether retrieving the struct field recursively.
|
||||
//
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
@ -91,10 +92,11 @@ func (c *Client) Post(url string, data ...interface{}) (resp *ClientResponse, er
|
||||
// Custom Content-Type.
|
||||
req.Header.Set("Content-Type", v)
|
||||
} else {
|
||||
// Auto detecting and setting the post content format: JSON.
|
||||
if json.Valid(paramBytes) {
|
||||
// Auto detecting and setting the post content format: JSON.
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
} else {
|
||||
} else if gregex.IsMatchString(`^[\w\[\]]+=.+`, param) {
|
||||
// If the parameters passed like "name=value", it then uses form type.
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,15 +7,41 @@
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
"github.com/gogf/gf/encoding/gurl"
|
||||
"github.com/gogf/gf/encoding/gxml"
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"github.com/gogf/gf/util/gvalid"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// xmlHeaderBytes is the most common XML format header.
|
||||
xmlHeaderBytes = []byte("<?xml")
|
||||
)
|
||||
|
||||
// Parse calls r.GetStruct to convert the parameters, which are sent from client,
|
||||
// to given struct, and then calls gvalid.CheckStruct validating the struct according
|
||||
// to the validation tag of the struct.
|
||||
//
|
||||
// See GetStruct, gvalid.CheckStruct.
|
||||
func (r *Request) Parse(pointer interface{}) error {
|
||||
if err := r.GetStruct(pointer); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gvalid.CheckStruct(pointer, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get is alias of GetRequest, which is one of the most commonly used functions for
|
||||
// retrieving parameter.
|
||||
// See GetRequest.
|
||||
@ -131,18 +157,18 @@ func (r *Request) GetMapStrStr(def ...map[string]interface{}) map[string]string
|
||||
// GetStruct is alias of GetRequestToStruct.
|
||||
// See GetRequestToStruct.
|
||||
func (r *Request) GetStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
return r.GetRequestToStruct(pointer, mapping...)
|
||||
return r.GetRequestStruct(pointer, mapping...)
|
||||
}
|
||||
|
||||
// GetToStruct is alias of GetRequestToStruct.
|
||||
// See GetRequestToStruct.
|
||||
// Deprecated.
|
||||
func (r *Request) GetToStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
return r.GetRequestToStruct(pointer, mapping...)
|
||||
return r.GetRequestStruct(pointer, mapping...)
|
||||
}
|
||||
|
||||
// ParseQuery parses query string into r.queryMap.
|
||||
func (r *Request) ParseQuery() {
|
||||
// parseQuery parses query string into r.queryMap.
|
||||
func (r *Request) parseQuery() {
|
||||
if r.parsedQuery {
|
||||
return
|
||||
}
|
||||
@ -157,19 +183,38 @@ func (r *Request) ParseQuery() {
|
||||
}
|
||||
|
||||
// ParseRaw parses the request raw data into r.rawMap.
|
||||
func (r *Request) ParseBody() {
|
||||
// Note that it also supports JSON data from client request.
|
||||
func (r *Request) parseBody() {
|
||||
if r.parsedBody {
|
||||
return
|
||||
}
|
||||
r.parsedBody = true
|
||||
if body := r.GetBodyString(); len(body) > 0 {
|
||||
r.bodyMap, _ = gstr.Parse(body)
|
||||
if body := r.GetBody(); len(body) > 0 {
|
||||
// Trim space/new line characters.
|
||||
body = bytes.TrimSpace(body)
|
||||
// JSON format checks.
|
||||
if body[0] == '{' && body[len(body)-1] == '}' {
|
||||
_ = json.Unmarshal(body, &r.bodyMap)
|
||||
}
|
||||
// XML format checks.
|
||||
if len(body) > 5 && bytes.EqualFold(body[:5], xmlHeaderBytes) {
|
||||
r.bodyMap, _ = gxml.DecodeWithoutRoot(body)
|
||||
}
|
||||
if body[0] == '<' && body[len(body)-1] == '>' {
|
||||
r.bodyMap, _ = gxml.DecodeWithoutRoot(body)
|
||||
}
|
||||
// Default parameters decoding.
|
||||
if r.bodyMap == nil {
|
||||
r.bodyMap, _ = gstr.Parse(r.GetBodyString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ParseForm parses the request form for HTTP method PUT, POST, PATCH.
|
||||
// parseForm parses the request form for HTTP method PUT, POST, PATCH.
|
||||
// The form data is pared into r.formMap.
|
||||
func (r *Request) ParseForm() {
|
||||
//
|
||||
// Note that if the form was parsed firstly, the request body would be cleared and empty.
|
||||
func (r *Request) parseForm() {
|
||||
if r.parsedForm {
|
||||
return
|
||||
}
|
||||
@ -191,6 +236,15 @@ func (r *Request) ParseForm() {
|
||||
// Re-parse the form data using united parsing way.
|
||||
params := ""
|
||||
for name, values := range r.PostForm {
|
||||
// Invalid parameter name.
|
||||
if !gregex.IsMatchString(`^[\w\[\]]+$`, name) {
|
||||
if len(r.PostForm) == 1 {
|
||||
// It might be JSON/XML content.
|
||||
r.bodyContent = gconv.UnsafeStrToBytes(name + strings.Join(values, " "))
|
||||
}
|
||||
params = ""
|
||||
break
|
||||
}
|
||||
if len(values) == 1 {
|
||||
if len(params) > 0 {
|
||||
params += "&"
|
||||
@ -216,8 +270,9 @@ func (r *Request) ParseForm() {
|
||||
if r.formMap, err = gstr.Parse(params); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
r.ParseBody()
|
||||
}
|
||||
if r.formMap == nil {
|
||||
r.parseBody()
|
||||
if len(r.bodyMap) > 0 {
|
||||
r.formMap = r.bodyMap
|
||||
}
|
||||
@ -227,7 +282,7 @@ func (r *Request) ParseForm() {
|
||||
|
||||
// GetMultipartForm parses and returns the form as multipart form.
|
||||
func (r *Request) GetMultipartForm() *multipart.Form {
|
||||
r.ParseForm()
|
||||
r.parseForm()
|
||||
return r.MultipartForm
|
||||
}
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ import (
|
||||
|
||||
// SetForm sets custom form value with key-value pair.
|
||||
func (r *Request) SetForm(key string, value interface{}) {
|
||||
r.ParseForm()
|
||||
r.parseForm()
|
||||
if r.formMap == nil {
|
||||
r.formMap = make(map[string]interface{})
|
||||
}
|
||||
@ -25,7 +25,7 @@ func (r *Request) SetForm(key string, value interface{}) {
|
||||
// It returns <def> if <key> does not exist in the form.
|
||||
// It returns nil if <def> is not passed.
|
||||
func (r *Request) GetForm(key string, def ...interface{}) interface{} {
|
||||
r.ParseForm()
|
||||
r.parseForm()
|
||||
if len(r.formMap) > 0 {
|
||||
if v, ok := r.formMap[key]; ok {
|
||||
return v
|
||||
@ -105,7 +105,7 @@ func (r *Request) GetFormInterfaces(key string, def ...interface{}) []interface{
|
||||
// The parameter <kvMap> specifies the keys retrieving from client parameters,
|
||||
// the associated values are the default values if the client does not pass.
|
||||
func (r *Request) GetFormMap(kvMap ...map[string]interface{}) map[string]interface{} {
|
||||
r.ParseForm()
|
||||
r.parseForm()
|
||||
if len(kvMap) > 0 && kvMap[0] != nil {
|
||||
if len(r.formMap) == 0 {
|
||||
return kvMap[0]
|
||||
@ -158,7 +158,7 @@ func (r *Request) GetFormMapStrVar(kvMap ...map[string]interface{}) map[string]*
|
||||
// given struct object. Note that the parameter <pointer> is a pointer to the struct object.
|
||||
// The optional parameter <mapping> is used to specify the key to attribute mapping.
|
||||
func (r *Request) GetFormStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
r.ParseForm()
|
||||
r.parseForm()
|
||||
tagMap := structs.TagMapName(pointer, paramTagPriority, true)
|
||||
if len(mapping) > 0 {
|
||||
for k, v := range mapping[0] {
|
||||
|
||||
@ -18,14 +18,16 @@ import (
|
||||
//
|
||||
// Note that if there're multiple parameters with the same name, the parameters are retrieved and overwrote
|
||||
// in order of priority: form > body.
|
||||
//
|
||||
// Deprecated.
|
||||
func (r *Request) GetPost(key string, def ...interface{}) interface{} {
|
||||
r.ParseForm()
|
||||
r.parseForm()
|
||||
if len(r.formMap) > 0 {
|
||||
if v, ok := r.formMap[key]; ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
r.ParseBody()
|
||||
r.parseBody()
|
||||
if len(r.bodyMap) > 0 {
|
||||
if v, ok := r.bodyMap[key]; ok {
|
||||
return v
|
||||
@ -37,66 +39,82 @@ func (r *Request) GetPost(key string, def ...interface{}) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostVar(key string, def ...interface{}) *gvar.Var {
|
||||
return gvar.New(r.GetPost(key, def...))
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostString(key string, def ...interface{}) string {
|
||||
return r.GetPostVar(key, def...).String()
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostBool(key string, def ...interface{}) bool {
|
||||
return r.GetPostVar(key, def...).Bool()
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostInt(key string, def ...interface{}) int {
|
||||
return r.GetPostVar(key, def...).Int()
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostInt32(key string, def ...interface{}) int32 {
|
||||
return r.GetPostVar(key, def...).Int32()
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostInt64(key string, def ...interface{}) int64 {
|
||||
return r.GetPostVar(key, def...).Int64()
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostInts(key string, def ...interface{}) []int {
|
||||
return r.GetPostVar(key, def...).Ints()
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostUint(key string, def ...interface{}) uint {
|
||||
return r.GetPostVar(key, def...).Uint()
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostUint32(key string, def ...interface{}) uint32 {
|
||||
return r.GetPostVar(key, def...).Uint32()
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostUint64(key string, def ...interface{}) uint64 {
|
||||
return r.GetPostVar(key, def...).Uint64()
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostFloat32(key string, def ...interface{}) float32 {
|
||||
return r.GetPostVar(key, def...).Float32()
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostFloat64(key string, def ...interface{}) float64 {
|
||||
return r.GetPostVar(key, def...).Float64()
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostFloats(key string, def ...interface{}) []float64 {
|
||||
return r.GetPostVar(key, def...).Floats()
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostArray(key string, def ...interface{}) []string {
|
||||
return r.GetPostVar(key, def...).Strings()
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostStrings(key string, def ...interface{}) []string {
|
||||
return r.GetPostVar(key, def...).Strings()
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostInterfaces(key string, def ...interface{}) []interface{} {
|
||||
return r.GetPostVar(key, def...).Interfaces()
|
||||
}
|
||||
@ -107,9 +125,11 @@ func (r *Request) GetPostInterfaces(key string, def ...interface{}) []interface{
|
||||
//
|
||||
// Note that if there're multiple parameters with the same name, the parameters are retrieved and overwrote
|
||||
// in order of priority: form > body.
|
||||
//
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostMap(kvMap ...map[string]interface{}) map[string]interface{} {
|
||||
r.ParseForm()
|
||||
r.ParseBody()
|
||||
r.parseForm()
|
||||
r.parseBody()
|
||||
var ok, filter bool
|
||||
if len(kvMap) > 0 && kvMap[0] != nil {
|
||||
filter = true
|
||||
@ -146,6 +166,8 @@ func (r *Request) GetPostMap(kvMap ...map[string]interface{}) map[string]interfa
|
||||
// as map[string]string. The parameter <kvMap> specifies the keys
|
||||
// retrieving from client parameters, the associated values are the default values if the client
|
||||
// does not pass.
|
||||
//
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostMapStrStr(kvMap ...map[string]interface{}) map[string]string {
|
||||
postMap := r.GetPostMap(kvMap...)
|
||||
if len(postMap) > 0 {
|
||||
@ -162,6 +184,8 @@ func (r *Request) GetPostMapStrStr(kvMap ...map[string]interface{}) map[string]s
|
||||
// as map[string]*gvar.Var. The parameter <kvMap> specifies the keys
|
||||
// retrieving from client parameters, the associated values are the default values if the client
|
||||
// does not pass.
|
||||
//
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostMapStrVar(kvMap ...map[string]interface{}) map[string]*gvar.Var {
|
||||
postMap := r.GetPostMap(kvMap...)
|
||||
if len(postMap) > 0 {
|
||||
@ -178,6 +202,8 @@ func (r *Request) GetPostMapStrVar(kvMap ...map[string]interface{}) map[string]*
|
||||
// and converts them to given struct object. Note that the parameter <pointer> is a pointer
|
||||
// to the struct object. The optional parameter <mapping> is used to specify the key to
|
||||
// attribute mapping.
|
||||
//
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
tagMap := structs.TagMapName(pointer, paramTagPriority, true)
|
||||
if len(mapping) > 0 {
|
||||
@ -189,6 +215,7 @@ func (r *Request) GetPostStruct(pointer interface{}, mapping ...map[string]strin
|
||||
}
|
||||
|
||||
// GetPostToStruct is alias of GetQueryStruct. See GetPostStruct.
|
||||
//
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostToStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
return r.GetPostStruct(pointer, mapping...)
|
||||
|
||||
@ -15,7 +15,7 @@ import (
|
||||
|
||||
// SetQuery sets custom query value with key-value pair.
|
||||
func (r *Request) SetQuery(key string, value interface{}) {
|
||||
r.ParseQuery()
|
||||
r.parseQuery()
|
||||
if r.queryMap == nil {
|
||||
r.queryMap = make(map[string]interface{})
|
||||
}
|
||||
@ -29,13 +29,13 @@ func (r *Request) SetQuery(key string, value interface{}) {
|
||||
// Note that if there're multiple parameters with the same name, the parameters are retrieved and overwrote
|
||||
// in order of priority: query > body.
|
||||
func (r *Request) GetQuery(key string, def ...interface{}) interface{} {
|
||||
r.ParseQuery()
|
||||
r.parseQuery()
|
||||
if len(r.queryMap) > 0 {
|
||||
if v, ok := r.queryMap[key]; ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
r.ParseBody()
|
||||
r.parseBody()
|
||||
if len(r.bodyMap) > 0 {
|
||||
if v, ok := r.bodyMap[key]; ok {
|
||||
return v
|
||||
@ -118,8 +118,8 @@ func (r *Request) GetQueryInterfaces(key string, def ...interface{}) []interface
|
||||
// Note that if there're multiple parameters with the same name, the parameters are retrieved and overwrote
|
||||
// in order of priority: query > body.
|
||||
func (r *Request) GetQueryMap(kvMap ...map[string]interface{}) map[string]interface{} {
|
||||
r.ParseQuery()
|
||||
r.ParseBody()
|
||||
r.parseQuery()
|
||||
r.parseBody()
|
||||
var m map[string]interface{}
|
||||
if len(kvMap) > 0 && kvMap[0] != nil {
|
||||
if len(r.queryMap) == 0 && len(r.bodyMap) == 0 {
|
||||
@ -193,7 +193,7 @@ func (r *Request) GetQueryMapStrVar(kvMap ...map[string]interface{}) map[string]
|
||||
// to the struct object. The optional parameter <mapping> is used to specify the key to
|
||||
// attribute mapping.
|
||||
func (r *Request) GetQueryStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
r.ParseQuery()
|
||||
r.parseQuery()
|
||||
tagMap := structs.TagMapName(pointer, paramTagPriority, true)
|
||||
if len(mapping) > 0 {
|
||||
for k, v := range mapping[0] {
|
||||
|
||||
@ -26,7 +26,7 @@ func (r *Request) GetRequest(key string, def ...interface{}) interface{} {
|
||||
value = r.GetForm(key)
|
||||
}
|
||||
if value == nil {
|
||||
r.ParseBody()
|
||||
r.parseBody()
|
||||
if len(r.bodyMap) > 0 {
|
||||
value = r.bodyMap[key]
|
||||
}
|
||||
@ -168,9 +168,9 @@ func (r *Request) GetRequestInterfaces(key string, def ...interface{}) []interfa
|
||||
// Note that if there're multiple parameters with the same name, the parameters are retrieved and overwrote
|
||||
// in order of priority: router < query < body < form < custom.
|
||||
func (r *Request) GetRequestMap(kvMap ...map[string]interface{}) map[string]interface{} {
|
||||
r.ParseQuery()
|
||||
r.ParseForm()
|
||||
r.ParseBody()
|
||||
r.parseQuery()
|
||||
r.parseForm()
|
||||
r.parseBody()
|
||||
var ok, filter bool
|
||||
var length int
|
||||
if len(kvMap) > 0 && kvMap[0] != nil {
|
||||
|
||||
@ -103,6 +103,14 @@ func (r *Response) WriteflnExit(format string, params ...interface{}) {
|
||||
|
||||
// WriteJson writes <content> to the response with JSON format.
|
||||
func (r *Response) WriteJson(content interface{}) error {
|
||||
// If given string/[]byte, response it directly to client.
|
||||
switch content.(type) {
|
||||
case string, []byte:
|
||||
r.Header().Set("Content-Type", "application/json")
|
||||
r.Write(content)
|
||||
return nil
|
||||
}
|
||||
// Else use json.Marshal function to encode the parameter.
|
||||
if b, err := json.Marshal(content); err != nil {
|
||||
return err
|
||||
} else {
|
||||
@ -127,6 +135,14 @@ func (r *Response) WriteJsonExit(content interface{}) error {
|
||||
//
|
||||
// Note that there should be a "callback" parameter in the request for JSONP format.
|
||||
func (r *Response) WriteJsonP(content interface{}) error {
|
||||
// If given string/[]byte, response it directly to client.
|
||||
switch content.(type) {
|
||||
case string, []byte:
|
||||
r.Header().Set("Content-Type", "application/json")
|
||||
r.Write(content)
|
||||
return nil
|
||||
}
|
||||
// Else use json.Marshal function to encode the parameter.
|
||||
if b, err := json.Marshal(content); err != nil {
|
||||
return err
|
||||
} else {
|
||||
@ -159,6 +175,14 @@ func (r *Response) WriteJsonPExit(content interface{}) error {
|
||||
|
||||
// WriteXml writes <content> to the response with XML format.
|
||||
func (r *Response) WriteXml(content interface{}, rootTag ...string) error {
|
||||
// If given string/[]byte, response it directly to client.
|
||||
switch content.(type) {
|
||||
case string, []byte:
|
||||
r.Header().Set("Content-Type", "application/xml")
|
||||
r.Write(content)
|
||||
return nil
|
||||
}
|
||||
// Else use gparser.VarToXml function to encode the parameter.
|
||||
if b, err := gparser.VarToXml(content, rootTag...); err != nil {
|
||||
return err
|
||||
} else {
|
||||
|
||||
@ -24,13 +24,15 @@ func (s *Server) handleAccessLog(r *Request) {
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
s.config.Logger.File(s.config.AccessLogPattern).Stdout(s.config.LogStdout).Printf(
|
||||
`%d "%s %s %s %s %s" %.3f, %s, "%s", "%s"`,
|
||||
r.Response.Status,
|
||||
r.Method, scheme, r.Host, r.URL.String(), r.Proto,
|
||||
float64(r.LeaveTime-r.EnterTime)/1000,
|
||||
r.GetClientIp(), r.Referer(), r.UserAgent(),
|
||||
)
|
||||
s.config.Logger.File(s.config.AccessLogPattern).
|
||||
Stdout(s.config.LogStdout).
|
||||
Printf(
|
||||
`%d "%s %s %s %s %s" %.3f, %s, "%s", "%s"`,
|
||||
r.Response.Status,
|
||||
r.Method, scheme, r.Host, r.URL.String(), r.Proto,
|
||||
float64(r.LeaveTime-r.EnterTime)/1000,
|
||||
r.GetClientIp(), r.Referer(), r.UserAgent(),
|
||||
)
|
||||
}
|
||||
|
||||
// 处理服务错误信息,主要是panic,http请求的status由access log进行管理
|
||||
@ -52,12 +54,15 @@ func (s *Server) handleErrorLog(err error, r *Request) {
|
||||
)
|
||||
if stack := gerror.Stack(err); stack != "" {
|
||||
content += "\nStack:\n" + stack
|
||||
s.config.Logger.File(s.config.AccessLogPattern).Stack(false).Stdout(s.config.LogStdout).Error(content)
|
||||
s.config.Logger.File(s.config.AccessLogPattern).
|
||||
Stack(false).
|
||||
Stdout(s.config.LogStdout).
|
||||
Error(content)
|
||||
return
|
||||
}
|
||||
s.config.Logger.File(s.config.AccessLogPattern).
|
||||
Stack(s.config.ErrorStack).
|
||||
StackWithFilter(gPATH_FILTER_KEY).
|
||||
Stdout(s.config.LogStdout).
|
||||
Errorf(content)
|
||||
Error(content)
|
||||
}
|
||||
|
||||
@ -41,3 +41,9 @@ func (s *Server) BindMiddlewareDefault(handlers ...HandlerFunc) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Use is alias of BindMiddlewareDefault.
|
||||
// See BindMiddlewareDefault.
|
||||
func (s *Server) Use(handlers ...HandlerFunc) {
|
||||
s.BindMiddlewareDefault(handlers...)
|
||||
}
|
||||
|
||||
@ -17,7 +17,51 @@ import (
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func Test_Params_Json(t *testing.T) {
|
||||
func Test_Params_Json_Request(t *testing.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string
|
||||
Time *time.Time
|
||||
Pass1 string `p:"password1"`
|
||||
Pass2 string `p:"password2" v:"required|length:2,20|password3|same:password1#||密码强度不足|两次密码不一致"`
|
||||
}
|
||||
p := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/get", func(r *ghttp.Request) {
|
||||
r.Response.WriteExit(r.Get("id"), r.Get("name"))
|
||||
})
|
||||
s.BindHandler("/map", func(r *ghttp.Request) {
|
||||
if m := r.GetMap(); len(m) > 0 {
|
||||
r.Response.WriteExit(m["id"], m["name"], m["password1"], m["password2"])
|
||||
}
|
||||
})
|
||||
s.BindHandler("/parse", func(r *ghttp.Request) {
|
||||
if m := r.GetMap(); len(m) > 0 {
|
||||
var user *User
|
||||
if err := r.Parse(&user); err != nil {
|
||||
r.Response.WriteExit(err)
|
||||
}
|
||||
r.Response.WriteExit(user.Id, user.Name, user.Pass1, user.Pass2)
|
||||
}
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.Case(t, func() {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
|
||||
gtest.Assert(client.GetContent("/get", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}`), `1john`)
|
||||
gtest.Assert(client.GetContent("/map", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}`), `1john123Abc!@#123Abc!@#`)
|
||||
gtest.Assert(client.PostContent("/parse", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}`), `1john123Abc!@#123Abc!@#`)
|
||||
gtest.Assert(client.PostContent("/parse", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123"}`), `密码强度不足; 两次密码不一致`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_Json_Response(t *testing.T) {
|
||||
type User struct {
|
||||
Uid int
|
||||
Name string
|
||||
|
||||
@ -23,33 +23,49 @@ func Test_Params_Struct(t *testing.T) {
|
||||
Id int
|
||||
Name string
|
||||
Time *time.Time
|
||||
Pass1 string `params:"password1"`
|
||||
Pass2 string `params:"password2" gvalid:"passwd1 @required|length:2,20|password3#||密码强度不足"`
|
||||
Pass1 string `p:"password1"`
|
||||
Pass2 string `p:"password2" v:"passwd1 @required|length:2,20|password3#||密码强度不足"`
|
||||
}
|
||||
p := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/struct1", func(r *ghttp.Request) {
|
||||
if m := r.GetMap(); len(m) > 0 {
|
||||
user := new(User)
|
||||
r.GetToStruct(user)
|
||||
r.Response.Write(user.Id, user.Name, user.Pass1, user.Pass2)
|
||||
if err := r.GetStruct(user); err != nil {
|
||||
r.Response.WriteExit(err)
|
||||
}
|
||||
r.Response.WriteExit(user.Id, user.Name, user.Pass1, user.Pass2)
|
||||
}
|
||||
})
|
||||
s.BindHandler("/struct2", func(r *ghttp.Request) {
|
||||
if m := r.GetMap(); len(m) > 0 {
|
||||
user := (*User)(nil)
|
||||
r.GetToStruct(&user)
|
||||
if err := r.GetStruct(&user); err != nil {
|
||||
r.Response.WriteExit(err)
|
||||
}
|
||||
if user != nil {
|
||||
r.Response.Write(user.Id, user.Name, user.Pass1, user.Pass2)
|
||||
r.Response.WriteExit(user.Id, user.Name, user.Pass1, user.Pass2)
|
||||
}
|
||||
}
|
||||
})
|
||||
s.BindHandler("/struct-valid", func(r *ghttp.Request) {
|
||||
if m := r.GetMap(); len(m) > 0 {
|
||||
user := new(User)
|
||||
r.GetToStruct(user)
|
||||
err := gvalid.CheckStruct(user, nil)
|
||||
r.Response.Write(err.Maps())
|
||||
if err := r.GetStruct(user); err != nil {
|
||||
r.Response.WriteExit(err)
|
||||
}
|
||||
if err := gvalid.CheckStruct(user, nil); err != nil {
|
||||
r.Response.WriteExit(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
s.BindHandler("/parse", func(r *ghttp.Request) {
|
||||
if m := r.GetMap(); len(m) > 0 {
|
||||
var user *User
|
||||
if err := r.Parse(&user); err != nil {
|
||||
r.Response.WriteExit(err)
|
||||
}
|
||||
r.Response.WriteExit(user.Id, user.Name, user.Pass1, user.Pass2)
|
||||
}
|
||||
})
|
||||
s.SetPort(p)
|
||||
@ -65,6 +81,10 @@ func Test_Params_Struct(t *testing.T) {
|
||||
gtest.Assert(client.PostContent("/struct1", `id=1&name=john&password1=123&password2=456`), `1john123456`)
|
||||
gtest.Assert(client.PostContent("/struct2", `id=1&name=john&password1=123&password2=456`), `1john123456`)
|
||||
gtest.Assert(client.PostContent("/struct2", ``), ``)
|
||||
gtest.Assert(client.PostContent("/struct-valid", `id=1&name=john&password1=123&password2=0`), `{"passwd1":{"length":"字段长度为2到20个字符","password3":"密码强度不足"}}`)
|
||||
gtest.Assert(client.PostContent("/struct-valid", `id=1&name=john&password1=123&password2=0`), `字段长度为2到20个字符; 密码强度不足`)
|
||||
gtest.Assert(client.PostContent("/parse", `id=1&name=john&password1=123&password2=0`), `字段长度为2到20个字符; 密码强度不足`)
|
||||
gtest.Assert(client.GetContent("/parse", `id=1&name=john&password1=123&password2=456`), `密码强度不足`)
|
||||
gtest.Assert(client.GetContent("/parse", `id=1&name=john&password1=123Abc!@#&password2=123Abc!@#`), `1john123Abc!@#123Abc!@#`)
|
||||
gtest.Assert(client.PostContent("/parse", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}`), `1john123Abc!@#123Abc!@#`)
|
||||
})
|
||||
}
|
||||
|
||||
65
net/ghttp/ghttp_unit_param_xml_test.go
Normal file
65
net/ghttp/ghttp_unit_param_xml_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func Test_Params_Xml_Request(t *testing.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string
|
||||
Time *time.Time
|
||||
Pass1 string `p:"password1"`
|
||||
Pass2 string `p:"password2" v:"required|length:2,20|password3|same:password1#||密码强度不足|两次密码不一致"`
|
||||
}
|
||||
p := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/get", func(r *ghttp.Request) {
|
||||
r.Response.WriteExit(r.Get("id"), r.Get("name"))
|
||||
})
|
||||
s.BindHandler("/map", func(r *ghttp.Request) {
|
||||
if m := r.GetMap(); len(m) > 0 {
|
||||
r.Response.WriteExit(m["id"], m["name"], m["password1"], m["password2"])
|
||||
}
|
||||
})
|
||||
s.BindHandler("/parse", func(r *ghttp.Request) {
|
||||
if m := r.GetMap(); len(m) > 0 {
|
||||
var user *User
|
||||
if err := r.Parse(&user); err != nil {
|
||||
r.Response.WriteExit(err)
|
||||
}
|
||||
r.Response.WriteExit(user.Id, user.Name, user.Pass1, user.Pass2)
|
||||
}
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.Case(t, func() {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
|
||||
content1 := `<doc><id>1</id><name>john</name><password1>123Abc!@#</password1><password2>123Abc!@#</password2></doc>`
|
||||
content2 := `<doc><id>1</id><name>john</name><password1>123Abc!@#</password1><password2>123</password2></doc>`
|
||||
gtest.Assert(client.GetContent("/get", content1), `1john`)
|
||||
gtest.Assert(client.PostContent("/get", content1), `1john`)
|
||||
gtest.Assert(client.GetContent("/map", content1), `1john123Abc!@#123Abc!@#`)
|
||||
gtest.Assert(client.PostContent("/map", content1), `1john123Abc!@#123Abc!@#`)
|
||||
gtest.Assert(client.PostContent("/parse", content1), `1john123Abc!@#123Abc!@#`)
|
||||
gtest.Assert(client.PostContent("/parse", content2), `密码强度不足; 两次密码不一致`)
|
||||
})
|
||||
}
|
||||
@ -10,9 +10,6 @@ package gfile
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
@ -101,7 +98,7 @@ func Join(paths ...string) string {
|
||||
|
||||
// Exists checks whether given <path> exist.
|
||||
func Exists(path string) bool {
|
||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||
if stat, err := os.Stat(path); stat != nil && !os.IsNotExist(err) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@ -160,111 +157,6 @@ func Rename(src string, dst string) error {
|
||||
return Move(src, dst)
|
||||
}
|
||||
|
||||
// Copy file/directory from <src> to <dst>.
|
||||
//
|
||||
// If <src> is file, it calls CopyFile to implements copy feature,
|
||||
// or else it calls CopyDir.
|
||||
func Copy(src string, dst string) error {
|
||||
if IsFile(src) {
|
||||
return CopyFile(src, dst)
|
||||
}
|
||||
return CopyDir(src, dst)
|
||||
}
|
||||
|
||||
// CopyFile copies the contents of the file named src to the file named
|
||||
// by dst. The file will be created if it does not already exist. If the
|
||||
// destination file exists, all it's contents will be replaced by the contents
|
||||
// of the source file. The file mode will be copied from the source and
|
||||
// the copied data is synced/flushed to stable storage.
|
||||
// Thanks: https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04
|
||||
func CopyFile(src, dst string) (err error) {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if e := in.Close(); e != nil {
|
||||
err = e
|
||||
}
|
||||
}()
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if e := out.Close(); e != nil {
|
||||
err = e
|
||||
}
|
||||
}()
|
||||
_, err = io.Copy(out, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = out.Sync()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
si, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = os.Chmod(dst, si.Mode())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CopyDir recursively copies a directory tree, attempting to preserve permissions.
|
||||
// Source directory must exist, destination directory must *not* exist.
|
||||
// Symlinks are ignored and skipped.
|
||||
func CopyDir(src string, dst string) (err error) {
|
||||
src = filepath.Clean(src)
|
||||
dst = filepath.Clean(dst)
|
||||
si, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !si.IsDir() {
|
||||
return fmt.Errorf("source is not a directory")
|
||||
}
|
||||
_, err = os.Stat(dst)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
return fmt.Errorf("destination already exists")
|
||||
}
|
||||
err = os.MkdirAll(dst, si.Mode())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
entries, err := ioutil.ReadDir(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, entry := range entries {
|
||||
srcPath := filepath.Join(src, entry.Name())
|
||||
dstPath := filepath.Join(dst, entry.Name())
|
||||
if entry.IsDir() {
|
||||
err = CopyDir(srcPath, dstPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Skip symlinks.
|
||||
if entry.Mode()&os.ModeSymlink != 0 {
|
||||
continue
|
||||
}
|
||||
err = CopyFile(srcPath, dstPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DirNames returns sub-file names of given directory <path>.
|
||||
// Note that the returned names are NOT absolute paths.
|
||||
func DirNames(path string) ([]string, error) {
|
||||
@ -322,7 +214,7 @@ func IsReadable(path string) bool {
|
||||
|
||||
// IsWritable checks whether given <path> is writable.
|
||||
//
|
||||
// @TODO improve performance; use golang.org/x/sys to cross-plat-form
|
||||
// TODO improve performance; use golang.org/x/sys to cross-plat-form
|
||||
func IsWritable(path string) bool {
|
||||
result := true
|
||||
if IsDir(path) {
|
||||
|
||||
134
os/gfile/gfile_copy.go
Normal file
134
os/gfile/gfile_copy.go
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gfile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Copy file/directory from <src> to <dst>.
|
||||
//
|
||||
// If <src> is file, it calls CopyFile to implements copy feature,
|
||||
// or else it calls CopyDir.
|
||||
func Copy(src string, dst string) error {
|
||||
if src == "" {
|
||||
return errors.New("source path cannot be empty")
|
||||
}
|
||||
if dst == "" {
|
||||
return errors.New("destination path cannot be empty")
|
||||
}
|
||||
if IsFile(src) {
|
||||
return CopyFile(src, dst)
|
||||
}
|
||||
return CopyDir(src, dst)
|
||||
}
|
||||
|
||||
// CopyFile copies the contents of the file named <src> to the file named
|
||||
// by <dst>. The file will be created if it does not exist. If the
|
||||
// destination file exists, all it's contents will be replaced by the contents
|
||||
// of the source file. The file mode will be copied from the source and
|
||||
// the copied data is synced/flushed to stable storage.
|
||||
// Thanks: https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04
|
||||
func CopyFile(src, dst string) (err error) {
|
||||
if src == "" {
|
||||
return errors.New("source file cannot be empty")
|
||||
}
|
||||
if dst == "" {
|
||||
return errors.New("destination file cannot be empty")
|
||||
}
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if e := in.Close(); e != nil {
|
||||
err = e
|
||||
}
|
||||
}()
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if e := out.Close(); e != nil {
|
||||
err = e
|
||||
}
|
||||
}()
|
||||
_, err = io.Copy(out, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = out.Sync()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
si, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = os.Chmod(dst, si.Mode())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CopyDir recursively copies a directory tree, attempting to preserve permissions.
|
||||
//
|
||||
// Note that, the Source directory must exist and symlinks are ignored and skipped.
|
||||
func CopyDir(src string, dst string) (err error) {
|
||||
if src == "" {
|
||||
return errors.New("source directory cannot be empty")
|
||||
}
|
||||
if dst == "" {
|
||||
return errors.New("destination directory cannot be empty")
|
||||
}
|
||||
src = filepath.Clean(src)
|
||||
dst = filepath.Clean(dst)
|
||||
si, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !si.IsDir() {
|
||||
return fmt.Errorf("source is not a directory")
|
||||
}
|
||||
if !Exists(dst) {
|
||||
err = os.MkdirAll(dst, si.Mode())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
entries, err := ioutil.ReadDir(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, entry := range entries {
|
||||
srcPath := filepath.Join(src, entry.Name())
|
||||
dstPath := filepath.Join(dst, entry.Name())
|
||||
if entry.IsDir() {
|
||||
err = CopyDir(srcPath, dstPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Skip symlinks.
|
||||
if entry.Mode()&os.ModeSymlink != 0 {
|
||||
continue
|
||||
}
|
||||
err = CopyFile(srcPath, dstPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -10,39 +10,48 @@ import (
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
// Replace replaces content for files under <path>.
|
||||
// ReplaceFile replaces content for file <path>.
|
||||
func ReplaceFile(search, replace, path string) error {
|
||||
return PutContents(path, gstr.Replace(GetContents(path), search, replace))
|
||||
}
|
||||
|
||||
// ReplaceFileFunc replaces content for file <path> with callback function <f>.
|
||||
func ReplaceFileFunc(f func(path, content string) string, path string) error {
|
||||
data := GetContents(path)
|
||||
result := f(path, data)
|
||||
if len(data) != len(result) && data != result {
|
||||
return PutContents(path, result)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReplaceDir replaces content for files under <path>.
|
||||
// The parameter <pattern> specifies the file pattern which matches to be replaced.
|
||||
// It does replacement recursively if given parameter <recursive> is true.
|
||||
func Replace(search, replace, path, pattern string, recursive ...bool) error {
|
||||
func ReplaceDir(search, replace, path, pattern string, recursive ...bool) error {
|
||||
files, err := ScanDirFile(path, pattern, recursive...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, file := range files {
|
||||
if err = PutContents(file, gstr.Replace(GetContents(file), search, replace)); err != nil {
|
||||
if err = ReplaceFile(search, replace, file); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ReplaceFunc replaces content for files under <path> with callback function <f>.
|
||||
// ReplaceDirFunc replaces content for files under <path> with callback function <f>.
|
||||
// The parameter <pattern> specifies the file pattern which matches to be replaced.
|
||||
// It does replacement recursively if given parameter <recursive> is true.
|
||||
func ReplaceFunc(f func(path, content string) string, path, pattern string, recursive ...bool) error {
|
||||
func ReplaceDirFunc(f func(path, content string) string, path, pattern string, recursive ...bool) error {
|
||||
files, err := ScanDirFile(path, pattern, recursive...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := ""
|
||||
result := ""
|
||||
for _, file := range files {
|
||||
data = GetContents(file)
|
||||
result = f(file, data)
|
||||
if data != result {
|
||||
if err = PutContents(file, result); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ReplaceFileFunc(f, file); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
|
||||
132
os/gfile/gfile_z_copy_test.go
Normal file
132
os/gfile/gfile_z_copy_test.go
Normal file
@ -0,0 +1,132 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gfile_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Copy(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var (
|
||||
paths string = "/testfile_copyfile1.txt"
|
||||
topath string = "/testfile_copyfile2.txt"
|
||||
)
|
||||
|
||||
createTestFile(paths, "")
|
||||
defer delTestFiles(paths)
|
||||
|
||||
gtest.Assert(gfile.Copy(testpath()+paths, testpath()+topath), nil)
|
||||
defer delTestFiles(topath)
|
||||
|
||||
gtest.Assert(gfile.IsFile(testpath()+topath), true)
|
||||
gtest.AssertNE(gfile.Copy("", ""), nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CopyFile(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var (
|
||||
paths string = "/testfile_copyfile1.txt"
|
||||
topath string = "/testfile_copyfile2.txt"
|
||||
)
|
||||
|
||||
createTestFile(paths, "")
|
||||
defer delTestFiles(paths)
|
||||
|
||||
gtest.Assert(gfile.CopyFile(testpath()+paths, testpath()+topath), nil)
|
||||
defer delTestFiles(topath)
|
||||
|
||||
gtest.Assert(gfile.IsFile(testpath()+topath), true)
|
||||
gtest.AssertNE(gfile.CopyFile("", ""), nil)
|
||||
})
|
||||
// Content replacement.
|
||||
gtest.Case(t, func() {
|
||||
src := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr())
|
||||
dst := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr())
|
||||
srcContent := "1"
|
||||
dstContent := "1"
|
||||
gfile.PutContents(src, srcContent)
|
||||
gfile.PutContents(dst, dstContent)
|
||||
gtest.Assert(gfile.GetContents(src), srcContent)
|
||||
gtest.Assert(gfile.GetContents(dst), dstContent)
|
||||
|
||||
err := gfile.CopyFile(src, dst)
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(gfile.GetContents(src), srcContent)
|
||||
gtest.Assert(gfile.GetContents(dst), srcContent)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CopyDir(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var (
|
||||
dirpath1 string = "/testcopydir1"
|
||||
dirpath2 string = "/testcopydir2"
|
||||
)
|
||||
|
||||
havelist1 := []string{
|
||||
"t1.txt",
|
||||
"t2.txt",
|
||||
}
|
||||
|
||||
createDir(dirpath1)
|
||||
for _, v := range havelist1 {
|
||||
createTestFile(dirpath1+"/"+v, "")
|
||||
}
|
||||
defer delTestFiles(dirpath1)
|
||||
|
||||
yfolder := testpath() + dirpath1
|
||||
tofolder := testpath() + dirpath2
|
||||
|
||||
if gfile.IsDir(tofolder) {
|
||||
gtest.Assert(gfile.Remove(tofolder), nil)
|
||||
gtest.Assert(gfile.Remove(""), nil)
|
||||
}
|
||||
|
||||
gtest.Assert(gfile.CopyDir(yfolder, tofolder), nil)
|
||||
defer delTestFiles(tofolder)
|
||||
|
||||
// 检查复制后的旧文件夹是否真实存在
|
||||
gtest.Assert(gfile.IsDir(yfolder), true)
|
||||
|
||||
// 检查复制后的旧文件夹中的文件是否真实存在
|
||||
for _, v := range havelist1 {
|
||||
gtest.Assert(gfile.IsFile(yfolder+"/"+v), true)
|
||||
}
|
||||
|
||||
// 检查复制后的新文件夹是否真实存在
|
||||
gtest.Assert(gfile.IsDir(tofolder), true)
|
||||
|
||||
// 检查复制后的新文件夹中的文件是否真实存在
|
||||
for _, v := range havelist1 {
|
||||
gtest.Assert(gfile.IsFile(tofolder+"/"+v), true)
|
||||
}
|
||||
|
||||
gtest.Assert(gfile.Remove(tofolder), nil)
|
||||
gtest.Assert(gfile.Remove(""), nil)
|
||||
})
|
||||
// Content replacement.
|
||||
gtest.Case(t, func() {
|
||||
src := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr(), gtime.TimestampNanoStr())
|
||||
dst := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr(), gtime.TimestampNanoStr())
|
||||
srcContent := "1"
|
||||
dstContent := "1"
|
||||
gfile.PutContents(src, srcContent)
|
||||
gfile.PutContents(dst, dstContent)
|
||||
gtest.Assert(gfile.GetContents(src), srcContent)
|
||||
gtest.Assert(gfile.GetContents(dst), dstContent)
|
||||
|
||||
err := gfile.CopyDir(gfile.Dir(src), gfile.Dir(dst))
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(gfile.GetContents(src), srcContent)
|
||||
gtest.Assert(gfile.GetContents(dst), srcContent)
|
||||
})
|
||||
}
|
||||
@ -350,24 +350,6 @@ func Test_Rename(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func Test_Copy(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var (
|
||||
paths string = "/testfile_copyfile1.txt"
|
||||
topath string = "/testfile_copyfile2.txt"
|
||||
)
|
||||
|
||||
createTestFile(paths, "")
|
||||
defer delTestFiles(paths)
|
||||
|
||||
gtest.Assert(gfile.Copy(testpath()+paths, testpath()+topath), nil)
|
||||
defer delTestFiles(topath)
|
||||
|
||||
gtest.Assert(gfile.IsFile(testpath()+topath), true)
|
||||
gtest.AssertNE(gfile.Copy("", ""), nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DirNames(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var (
|
||||
@ -628,13 +610,7 @@ func Test_ExtName(t *testing.T) {
|
||||
|
||||
func Test_TempDir(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var (
|
||||
tpath string
|
||||
)
|
||||
|
||||
tpath = gfile.TempDir()
|
||||
gtest.Assert(tpath, os.TempDir())
|
||||
|
||||
gtest.Assert(gfile.TempDir(), "/tmp")
|
||||
})
|
||||
}
|
||||
|
||||
@ -688,71 +664,3 @@ func Test_MainPkgPath(t *testing.T) {
|
||||
gtest.Assert(reads, "")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CopyFile(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var (
|
||||
paths string = "/testfile_copyfile1.txt"
|
||||
topath string = "/testfile_copyfile2.txt"
|
||||
)
|
||||
|
||||
createTestFile(paths, "")
|
||||
defer delTestFiles(paths)
|
||||
|
||||
gtest.Assert(gfile.CopyFile(testpath()+paths, testpath()+topath), nil)
|
||||
defer delTestFiles(topath)
|
||||
|
||||
gtest.Assert(gfile.IsFile(testpath()+topath), true)
|
||||
gtest.AssertNE(gfile.CopyFile("", ""), nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CopyDir(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var (
|
||||
dirpath1 string = "/testcopydir1"
|
||||
dirpath2 string = "/testcopydir2"
|
||||
)
|
||||
|
||||
havelist1 := []string{
|
||||
"t1.txt",
|
||||
"t2.txt",
|
||||
}
|
||||
|
||||
createDir(dirpath1)
|
||||
for _, v := range havelist1 {
|
||||
createTestFile(dirpath1+"/"+v, "")
|
||||
}
|
||||
defer delTestFiles(dirpath1)
|
||||
|
||||
yfolder := testpath() + dirpath1
|
||||
tofolder := testpath() + dirpath2
|
||||
|
||||
if gfile.IsDir(tofolder) {
|
||||
gtest.Assert(gfile.Remove(tofolder), nil)
|
||||
gtest.Assert(gfile.Remove(""), nil)
|
||||
}
|
||||
|
||||
gtest.Assert(gfile.CopyDir(yfolder, tofolder), nil)
|
||||
defer delTestFiles(tofolder)
|
||||
|
||||
// 检查复制后的旧文件夹是否真实存在
|
||||
gtest.Assert(gfile.IsDir(yfolder), true)
|
||||
|
||||
// 检查复制后的旧文件夹中的文件是否真实存在
|
||||
for _, v := range havelist1 {
|
||||
gtest.Assert(gfile.IsFile(yfolder+"/"+v), true)
|
||||
}
|
||||
|
||||
// 检查复制后的新文件夹是否真实存在
|
||||
gtest.Assert(gfile.IsDir(tofolder), true)
|
||||
|
||||
// 检查复制后的新文件夹中的文件是否真实存在
|
||||
for _, v := range havelist1 {
|
||||
gtest.Assert(gfile.IsFile(tofolder+"/"+v), true)
|
||||
}
|
||||
|
||||
gtest.Assert(gfile.Remove(tofolder), nil)
|
||||
gtest.Assert(gfile.Remove(""), nil)
|
||||
})
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ func fileRealPath(path string) string {
|
||||
|
||||
// fileExists checks whether given <path> exist.
|
||||
func fileExists(path string) bool {
|
||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||
if stat, err := os.Stat(path); stat != nil && !os.IsNotExist(err) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@ -102,6 +102,7 @@ func ShellExec(cmd string, environment ...[]string) (string, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
p := NewProcess(getShell(), append([]string{getShellOption()}, parseCommand(cmd)...), environment...)
|
||||
p.Stdout = buf
|
||||
p.Stderr = buf
|
||||
err := p.Run()
|
||||
return buf.String(), err
|
||||
}
|
||||
@ -154,6 +155,14 @@ func getShell() string {
|
||||
case "windows":
|
||||
return SearchBinary("cmd.exe")
|
||||
default:
|
||||
// Check the default binary storage path.
|
||||
if gfile.Exists("/bin/bash") {
|
||||
return "/bin/bash"
|
||||
}
|
||||
if gfile.Exists("/bin/sh") {
|
||||
return "/bin/sh"
|
||||
}
|
||||
// Else search the env PATH.
|
||||
path := SearchBinary("bash")
|
||||
if path == "" {
|
||||
path = SearchBinary("sh")
|
||||
|
||||
@ -165,7 +165,7 @@ func (sp *SPath) Search(name string, indexFiles ...string) (filePath string, isD
|
||||
path := ""
|
||||
for _, v := range array {
|
||||
path = gfile.Join(v, name)
|
||||
if stat, err := os.Stat(path); !os.IsNotExist(err) {
|
||||
if stat, err := os.Stat(path); stat != nil && !os.IsNotExist(err) {
|
||||
path = gfile.Abs(path)
|
||||
// Security check: the result file path must be under the searching directory.
|
||||
if len(path) >= len(v) && path[:len(v)] == v {
|
||||
|
||||
@ -87,7 +87,15 @@ var (
|
||||
)
|
||||
|
||||
// SetTimeZone sets the time zone for current whole process.
|
||||
// The parameter <zone> is an area string specifying corresponding time zone, eg: Asia/Shanghai.
|
||||
// The parameter <zone> is an area string specifying corresponding time zone,
|
||||
// eg: Asia/Shanghai.
|
||||
//
|
||||
// Note that the time zone database needed by LoadLocation may not be
|
||||
// present on all systems, especially non-Unix systems.
|
||||
// LoadLocation looks in the directory or uncompressed zip file
|
||||
// named by the ZONEINFO environment variable, if any, then looks in
|
||||
// known installation locations on Unix systems,
|
||||
// and finally looks in $GOROOT/lib/time/zoneinfo.zip.
|
||||
func SetTimeZone(zone string) error {
|
||||
location, err := time.LoadLocation(zone)
|
||||
if err == nil {
|
||||
@ -96,26 +104,50 @@ func SetTimeZone(zone string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Timestamp returns the timestamp in seconds.
|
||||
// Timestamp retrieves and returns the timestamp in seconds.
|
||||
func Timestamp() int64 {
|
||||
return Now().Timestamp()
|
||||
}
|
||||
|
||||
// TimestampMilli returns the timestamp in milliseconds.
|
||||
// TimestampMilli retrieves and returns the timestamp in milliseconds.
|
||||
func TimestampMilli() int64 {
|
||||
return Now().TimestampMilli()
|
||||
}
|
||||
|
||||
// TimestampMicro returns the timestamp in microseconds.
|
||||
// TimestampMicro retrieves and returns the timestamp in microseconds.
|
||||
func TimestampMicro() int64 {
|
||||
return Now().TimestampMicro()
|
||||
}
|
||||
|
||||
// TimestampNano returns the timestamp in nanoseconds.
|
||||
// TimestampNano retrieves and returns the timestamp in nanoseconds.
|
||||
func TimestampNano() int64 {
|
||||
return Now().TimestampNano()
|
||||
}
|
||||
|
||||
// TimestampStr is a convenience method which retrieves and returns
|
||||
// the timestamp in seconds as string.
|
||||
func TimestampStr() string {
|
||||
return Now().TimestampStr()
|
||||
}
|
||||
|
||||
// TimestampMilliStr is a convenience method which retrieves and returns
|
||||
// the timestamp in milliseconds as string.
|
||||
func TimestampMilliStr() string {
|
||||
return Now().TimestampMilliStr()
|
||||
}
|
||||
|
||||
// TimestampMicroStr is a convenience method which retrieves and returns
|
||||
// the timestamp in microseconds as string.
|
||||
func TimestampMicroStr() string {
|
||||
return Now().TimestampMicroStr()
|
||||
}
|
||||
|
||||
// TimestampNanoStr is a convenience method which retrieves and returns
|
||||
// the timestamp in nanoseconds as string.
|
||||
func TimestampNanoStr() string {
|
||||
return Now().TimestampNanoStr()
|
||||
}
|
||||
|
||||
// Second returns the timestamp in seconds.
|
||||
// Deprecated, use Timestamp instead.
|
||||
func Second() int64 {
|
||||
|
||||
@ -8,6 +8,7 @@ package gtime
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -98,6 +99,30 @@ func (t *Time) TimestampNano() int64 {
|
||||
return t.UnixNano()
|
||||
}
|
||||
|
||||
// TimestampStr is a convenience method which retrieves and returns
|
||||
// the timestamp in seconds as string.
|
||||
func (t *Time) TimestampStr() string {
|
||||
return strconv.FormatInt(t.Timestamp(), 10)
|
||||
}
|
||||
|
||||
// TimestampMilliStr is a convenience method which retrieves and returns
|
||||
// the timestamp in milliseconds as string.
|
||||
func (t *Time) TimestampMilliStr() string {
|
||||
return strconv.FormatInt(t.TimestampMilli(), 10)
|
||||
}
|
||||
|
||||
// TimestampMicroStr is a convenience method which retrieves and returns
|
||||
// the timestamp in microseconds as string.
|
||||
func (t *Time) TimestampMicroStr() string {
|
||||
return strconv.FormatInt(t.TimestampMicro(), 10)
|
||||
}
|
||||
|
||||
// TimestampNanoStr is a convenience method which retrieves and returns
|
||||
// the timestamp in nanoseconds as string.
|
||||
func (t *Time) TimestampNanoStr() string {
|
||||
return strconv.FormatInt(t.TimestampNano(), 10)
|
||||
}
|
||||
|
||||
// Second returns the second offset within the minute specified by t,
|
||||
// in the range [0, 59].
|
||||
func (t *Time) Second() int {
|
||||
|
||||
@ -31,6 +31,7 @@ type View struct {
|
||||
defaultFile string // Default template file for parsing.
|
||||
i18nManager *gi18n.Manager // I18n manager for this view.
|
||||
delimiters []string // Custom template delimiters.
|
||||
config Config // Extra configuration for the view.
|
||||
}
|
||||
|
||||
// Params is type for template params.
|
||||
@ -124,9 +125,13 @@ func New(path ...string) *View {
|
||||
view.BindFunc("gt", view.funcGt)
|
||||
view.BindFunc("ge", view.funcGe)
|
||||
view.BindFunc("text", view.funcText)
|
||||
|
||||
view.BindFunc("html", view.funcHtmlEncode)
|
||||
view.BindFunc("htmlencode", view.funcHtmlEncode)
|
||||
view.BindFunc("htmldecode", view.funcHtmlDecode)
|
||||
view.BindFunc("encode", view.funcHtmlEncode)
|
||||
view.BindFunc("decode", view.funcHtmlDecode)
|
||||
|
||||
view.BindFunc("url", view.funcUrlEncode)
|
||||
view.BindFunc("urlencode", view.funcUrlEncode)
|
||||
view.BindFunc("urldecode", view.funcUrlDecode)
|
||||
|
||||
@ -24,6 +24,7 @@ type Config struct {
|
||||
Data map[string]interface{} // Global template variables.
|
||||
DefaultFile string // Default template file for parsing.
|
||||
Delimiters []string // Custom template delimiters.
|
||||
AutoEncode bool // Automatically encodes and provides safe html output, which is good for avoiding XSS.
|
||||
}
|
||||
|
||||
// SetConfig sets the configuration for view.
|
||||
@ -45,6 +46,7 @@ func (view *View) SetConfig(config Config) error {
|
||||
if len(config.Delimiters) > 1 {
|
||||
view.SetDelimiters(config.Delimiters[0], config.Delimiters[1])
|
||||
}
|
||||
view.config = config
|
||||
// Clear global template object cache.
|
||||
// It's just cache, do not hesitate clearing it.
|
||||
templates.Clear()
|
||||
@ -200,6 +202,13 @@ func (view *View) SetDelimiters(left, right string) {
|
||||
view.delimiters[1] = right
|
||||
}
|
||||
|
||||
// SetAutoEncode enables/disables automatically html encoding feature.
|
||||
// When AutoEncode feature is enables, view engine automatically encodes and provides safe html output,
|
||||
// which is good for avoid XSS.
|
||||
func (view *View) SetAutoEncode(enable bool) {
|
||||
view.config.AutoEncode = enable
|
||||
}
|
||||
|
||||
// BindFunc registers customized global template function named <name>
|
||||
// with given function <function> to current view object.
|
||||
// The <name> is the function name which can be called in template content.
|
||||
|
||||
@ -17,9 +17,10 @@ import (
|
||||
"github.com/gogf/gf/os/gmlock"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
htmltpl "html/template"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
texttpl "text/template"
|
||||
|
||||
"github.com/gogf/gf/os/gres"
|
||||
|
||||
@ -34,6 +35,13 @@ const (
|
||||
gCONTENT_TEMPLATE_NAME = "TemplateContent"
|
||||
)
|
||||
|
||||
// fileCacheItem is the cache item for template file.
|
||||
type fileCacheItem struct {
|
||||
path string
|
||||
folder string
|
||||
content string
|
||||
}
|
||||
|
||||
var (
|
||||
// Templates cache map for template folder.
|
||||
// Note that there's no expiring logic for this map.
|
||||
@ -42,17 +50,10 @@ var (
|
||||
resourceTryFolders = []string{"template/", "template", "/template", "/template/"}
|
||||
)
|
||||
|
||||
// fileCacheItem is the cache item for template file.
|
||||
type fileCacheItem struct {
|
||||
path string
|
||||
folder string
|
||||
content string
|
||||
}
|
||||
|
||||
// Parse parses given template file <file> with given template variables <params>
|
||||
// and returns the parsed template content.
|
||||
func (view *View) Parse(file string, params ...Params) (result string, err error) {
|
||||
var tpl *template.Template
|
||||
var tpl interface{}
|
||||
// It caches the file, folder and its content to enhance performance.
|
||||
r := view.fileCacheMap.GetOrSetFuncLock(file, func() interface{} {
|
||||
var path, folder, content string
|
||||
@ -99,7 +100,11 @@ func (view *View) Parse(file string, params ...Params) (result string, err error
|
||||
}
|
||||
// Using memory lock to ensure concurrent safety for template parsing.
|
||||
gmlock.LockFunc("gview.Parse:"+item.path, func() {
|
||||
tpl, err = tpl.Parse(item.content)
|
||||
if view.config.AutoEncode {
|
||||
tpl, err = tpl.(*htmltpl.Template).Parse(item.content)
|
||||
} else {
|
||||
tpl, err = tpl.(*texttpl.Template).Parse(item.content)
|
||||
}
|
||||
})
|
||||
|
||||
// Note that the template variable assignment cannot change the value
|
||||
@ -133,9 +138,16 @@ func (view *View) Parse(file string, params ...Params) (result string, err error
|
||||
}
|
||||
}
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if err := tpl.Execute(buffer, variables); err != nil {
|
||||
return "", err
|
||||
if view.config.AutoEncode {
|
||||
if err := tpl.(*htmltpl.Template).Execute(buffer, variables); err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
if err := tpl.(*texttpl.Template).Execute(buffer, variables); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO any graceful plan to replace "<no value>"?
|
||||
result = gstr.Replace(buffer.String(), "<no value>", "")
|
||||
result = view.i18nTranslate(result, variables)
|
||||
@ -157,12 +169,19 @@ func (view *View) ParseContent(content string, params ...Params) (string, error)
|
||||
err := (error)(nil)
|
||||
key := fmt.Sprintf("%s_%v", gCONTENT_TEMPLATE_NAME, view.delimiters)
|
||||
tpl := templates.GetOrSetFuncLock(key, func() interface{} {
|
||||
return template.New(gCONTENT_TEMPLATE_NAME).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap)
|
||||
}).(*template.Template)
|
||||
if view.config.AutoEncode {
|
||||
return htmltpl.New(gCONTENT_TEMPLATE_NAME).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap)
|
||||
}
|
||||
return texttpl.New(gCONTENT_TEMPLATE_NAME).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap)
|
||||
})
|
||||
// Using memory lock to ensure concurrent safety for content parsing.
|
||||
hash := strconv.FormatUint(ghash.DJBHash64([]byte(content)), 10)
|
||||
gmlock.LockFunc("gview.ParseContent:"+hash, func() {
|
||||
tpl, err = tpl.Parse(content)
|
||||
if view.config.AutoEncode {
|
||||
tpl, err = tpl.(*htmltpl.Template).Parse(content)
|
||||
} else {
|
||||
tpl, err = tpl.(*texttpl.Template).Parse(content)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -198,8 +217,14 @@ func (view *View) ParseContent(content string, params ...Params) (string, error)
|
||||
}
|
||||
}
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if err := tpl.Execute(buffer, variables); err != nil {
|
||||
return "", err
|
||||
if view.config.AutoEncode {
|
||||
if err := tpl.(*htmltpl.Template).Execute(buffer, variables); err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
if err := tpl.(*texttpl.Template).Execute(buffer, variables); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
// TODO any graceful plan to replace "<no value>"?
|
||||
result := gstr.Replace(buffer.String(), "<no value>", "")
|
||||
@ -211,21 +236,32 @@ func (view *View) ParseContent(content string, params ...Params) (string, error)
|
||||
// It uses template cache to enhance performance, that is, it will return the same template object
|
||||
// with the same given <path>. It will also automatically refresh the template cache
|
||||
// if the template files under <path> changes (recursively).
|
||||
func (view *View) getTemplate(filePath, folderPath, pattern string) (tpl *template.Template, err error) {
|
||||
func (view *View) getTemplate(filePath, folderPath, pattern string) (tpl interface{}, err error) {
|
||||
// Key for template cache.
|
||||
key := fmt.Sprintf("%s_%v", filePath, view.delimiters)
|
||||
result := templates.GetOrSetFuncLock(key, func() interface{} {
|
||||
// Do not use <key> but the <path> as the parameter <name> for template.New,
|
||||
// because when error occurs the <name> will be printed out.
|
||||
tpl = template.New(filePath).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap)
|
||||
// Do not use <key> but the <filePath> as the parameter <name> for function New,
|
||||
// because when error occurs the <name> will be printed out for error locating.
|
||||
if view.config.AutoEncode {
|
||||
tpl = htmltpl.New(filePath).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap)
|
||||
} else {
|
||||
tpl = texttpl.New(filePath).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap)
|
||||
}
|
||||
// Firstly checking the resource manager.
|
||||
if !gres.IsEmpty() {
|
||||
if files := gres.ScanDirFile(folderPath, pattern, true); len(files) > 0 {
|
||||
var err error
|
||||
for _, v := range files {
|
||||
_, err = tpl.New(v.FileInfo().Name()).Option("missingkey=zero").Parse(string(v.Content()))
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
if view.config.AutoEncode {
|
||||
_, err = tpl.(*htmltpl.Template).New(v.FileInfo().Name()).Parse(string(v.Content()))
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
} else {
|
||||
_, err = tpl.(*texttpl.Template).New(v.FileInfo().Name()).Parse(string(v.Content()))
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return tpl
|
||||
@ -238,13 +274,19 @@ func (view *View) getTemplate(filePath, folderPath, pattern string) (tpl *templa
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if tpl, err = tpl.ParseFiles(files...); err != nil {
|
||||
return nil
|
||||
if view.config.AutoEncode {
|
||||
if tpl, err = tpl.(*htmltpl.Template).ParseFiles(files...); err != nil {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if tpl, err = tpl.(*texttpl.Template).ParseFiles(files...); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return tpl
|
||||
})
|
||||
if result != nil {
|
||||
return result.(*template.Template), nil
|
||||
return result, nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gview_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/encoding/ghtml"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"io/ioutil"
|
||||
@ -289,3 +290,25 @@ func Test_HotReload(t *testing.T) {
|
||||
gtest.Assert(result, `test2:2`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_XSS(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
v := gview.New()
|
||||
s := "<br>"
|
||||
r, err := v.ParseContent("{{.v}}", g.Map{
|
||||
"v": s,
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(r, s)
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
v := gview.New()
|
||||
v.SetAutoEncode(true)
|
||||
s := "<br>"
|
||||
r, err := v.ParseContent("{{.v}}", g.Map{
|
||||
"v": s,
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(r, ghtml.Entities(s))
|
||||
})
|
||||
}
|
||||
|
||||
@ -24,6 +24,9 @@ import (
|
||||
// a .[[b=c -> map[a___[b:c]
|
||||
//
|
||||
func Parse(s string) (result map[string]interface{}, err error) {
|
||||
if s == "" {
|
||||
return nil, nil
|
||||
}
|
||||
result = make(map[string]interface{})
|
||||
parts := strings.Split(s, "&")
|
||||
for _, part := range parts {
|
||||
|
||||
@ -10,7 +10,6 @@ package gconv
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@ -213,7 +212,8 @@ func String(i interface{}) string {
|
||||
}
|
||||
return value.String()
|
||||
default:
|
||||
if empty.IsNil(value) {
|
||||
// Empty checks.
|
||||
if value == nil {
|
||||
return ""
|
||||
}
|
||||
if f, ok := value.(apiString); ok {
|
||||
@ -225,6 +225,24 @@ func String(i interface{}) string {
|
||||
// then use that interface to perform the conversion
|
||||
return f.Error()
|
||||
} else {
|
||||
// Reflect checks.
|
||||
rv := reflect.ValueOf(value)
|
||||
kind := rv.Kind()
|
||||
switch kind {
|
||||
case reflect.Chan,
|
||||
reflect.Map,
|
||||
reflect.Slice,
|
||||
reflect.Func,
|
||||
reflect.Ptr,
|
||||
reflect.Interface,
|
||||
reflect.UnsafePointer:
|
||||
if rv.IsNil() {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
if kind == reflect.Ptr {
|
||||
return String(rv.Elem().Interface())
|
||||
}
|
||||
// Finally we use json.Marshal to convert.
|
||||
if jsonContent, err := json.Marshal(value); err != nil {
|
||||
return fmt.Sprint(value)
|
||||
|
||||
@ -26,13 +26,26 @@ type apiMapStrAny interface {
|
||||
// If <value> is a struct/*struct object, the second parameter <tags> specifies the most priority
|
||||
// tags that will be detected, otherwise it detects the tags in order of: gconv, json, and then the field name.
|
||||
func Map(value interface{}, tags ...string) map[string]interface{} {
|
||||
return doMapConvert(value, false, tags...)
|
||||
}
|
||||
|
||||
// MapDeep does Map function recursively, which means if the attribute of <value>
|
||||
// is also a struct/*struct, calls Map function on this attribute converting it to
|
||||
// a map[string]interface{} type variable.
|
||||
// Also see Map.
|
||||
func MapDeep(value interface{}, tags ...string) map[string]interface{} {
|
||||
return doMapConvert(value, true, tags...)
|
||||
}
|
||||
|
||||
// doMapConvert implements the map converting.
|
||||
func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]interface{} {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
if r, ok := value.(map[string]interface{}); ok {
|
||||
return r
|
||||
} else {
|
||||
// Only assert the common combination of types, and finally it uses reflection.
|
||||
// Assert the common combination of types, and finally it uses reflection.
|
||||
m := make(map[string]interface{})
|
||||
switch value.(type) {
|
||||
case map[interface{}]interface{}:
|
||||
@ -91,7 +104,7 @@ func Map(value interface{}, tags ...string) map[string]interface{} {
|
||||
for k, v := range value.(map[uint]string) {
|
||||
m[String(k)] = v
|
||||
}
|
||||
// Not a common type, use reflection
|
||||
// Not a common type, then use reflection.
|
||||
default:
|
||||
rv := reflect.ValueOf(value)
|
||||
kind := rv.Kind()
|
||||
@ -121,14 +134,19 @@ func Map(value interface{}, tags ...string) map[string]interface{} {
|
||||
default:
|
||||
tagArray = append(tags, structTagPriority...)
|
||||
}
|
||||
var rtField reflect.StructField
|
||||
var rvField reflect.Value
|
||||
var rvKind reflect.Kind
|
||||
for i := 0; i < rv.NumField(); i++ {
|
||||
rtField = rt.Field(i)
|
||||
rvField = rv.Field(i)
|
||||
// Only convert the public attributes.
|
||||
fieldName := rt.Field(i).Name
|
||||
fieldName := rtField.Name
|
||||
if !utilstr.IsLetterUpper(fieldName[0]) {
|
||||
continue
|
||||
}
|
||||
name = ""
|
||||
fieldTag := rt.Field(i).Tag
|
||||
fieldTag := rtField.Tag
|
||||
for _, tag := range tagArray {
|
||||
if name = fieldTag.Get(tag); name != "" {
|
||||
break
|
||||
@ -146,7 +164,7 @@ func Map(value interface{}, tags ...string) map[string]interface{} {
|
||||
if len(array) > 1 {
|
||||
switch strings.TrimSpace(array[1]) {
|
||||
case "omitempty":
|
||||
if empty.IsEmpty(rv.Field(i).Interface()) {
|
||||
if empty.IsEmpty(rvField.Interface()) {
|
||||
continue
|
||||
} else {
|
||||
name = strings.TrimSpace(array[0])
|
||||
@ -156,7 +174,22 @@ func Map(value interface{}, tags ...string) map[string]interface{} {
|
||||
}
|
||||
}
|
||||
}
|
||||
m[name] = rv.Field(i).Interface()
|
||||
if recursive {
|
||||
rvKind = rvField.Kind()
|
||||
if rvKind == reflect.Ptr {
|
||||
rvField = rvField.Elem()
|
||||
rvKind = rvField.Kind()
|
||||
}
|
||||
if rvKind == reflect.Struct {
|
||||
for k, v := range doMapConvert(rvField.Interface(), recursive, tags...) {
|
||||
m[k] = v
|
||||
}
|
||||
} else {
|
||||
m[name] = rvField.Interface()
|
||||
}
|
||||
} else {
|
||||
m[name] = rvField.Interface()
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
@ -166,30 +199,6 @@ func Map(value interface{}, tags ...string) map[string]interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
// MapDeep does Map function recursively, which means if the attribute of <value>
|
||||
// is also a struct/*struct, calls Map function on this attribute converting it to
|
||||
// a map[string]interface{} type variable.
|
||||
// Also see Map.
|
||||
func MapDeep(value interface{}, tags ...string) map[string]interface{} {
|
||||
data := Map(value, tags...)
|
||||
for key, value := range data {
|
||||
rv := reflect.ValueOf(value)
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Struct:
|
||||
delete(data, key)
|
||||
for k, v := range MapDeep(value, tags...) {
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// MapStrStr converts <value> to map[string]string.
|
||||
// Note that there might be data copy for this map type converting.
|
||||
func MapStrStr(value interface{}, tags ...string) map[string]string {
|
||||
|
||||
@ -940,7 +940,6 @@ func Test_Map_StructInherit_All(t *testing.T) {
|
||||
|
||||
func Test_Struct_Basic1_All(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
|
||||
type Score struct {
|
||||
Name int
|
||||
Result string
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user