Compare commits

...

19 Commits

Author SHA1 Message Date
506552c3a9 example codes update; donator updates 2020-01-04 17:19:50 +08:00
2bacc77224 improve JSON/XML parsing feature for ghttp.Request 2020-01-04 15:35:21 +08:00
bc53f265af improve gconv.Map/String 2020-01-03 20:23:10 +08:00
344f232c36 add Timestamp*Str functions for gtime; improve unit testing cases for gfile 2020-01-02 21:29:06 +08:00
27b677b0c0 improve Map converting feature for gconv; improve package gproc for local shell searching; improve JSON/XML response for ghttp.Response 2020-01-02 19:45:41 +08:00
a5a0e381bd add more examples for ghttp.Server 2020-01-01 16:45:43 +08:00
d528d7f5ab donator updates 2020-01-01 15:24:10 +08:00
821c71bd8d improve parameter parsing feature for ghttp.Request 2020-01-01 15:20:03 +08:00
604a10400d improve parameter parsing feature for ghttp.Request 2020-01-01 14:57:57 +08:00
9219471f67 improve parameter parsing feature for ghttp.Request 2020-01-01 14:26:00 +08:00
fe5d2e5685 improve parameter parsing feature for ghttp.Request 2020-01-01 14:18:00 +08:00
0a89daa513 add more unit testing cases for gdb.TX 2019-12-31 16:02:18 +08:00
5dbda8aedc fix issue in unit testing codes in gfile 2019-12-29 14:15:17 +08:00
134e4cf28f improve gfile.CopyDir function; add Model function for gdb.Model 2019-12-28 13:55:05 +08:00
56a85abef7 add AutoEncode feature for gview 2019-12-26 11:03:59 +08:00
80c6ceaf26 fix issue 437 2019-12-25 21:22:06 +08:00
a10f428715 add Iterator* functions for garray; add ReplaceDir*/ReplaceFile* functions for gfile; remove gfile.Replace/ReplaceFunc functions 2019-12-25 20:56:39 +08:00
597f7468e9 fix issue in prefix feature for method operations of gdb 2019-12-23 23:14:54 +08:00
5db8851213 comment update for gtype 2019-12-20 23:23:50 +08:00
106 changed files with 2252 additions and 609 deletions

View File

@ -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]

View File

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

View File

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

View File

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

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

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

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

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

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

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

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

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

View File

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

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

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

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

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

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

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"/>

View File

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

View File

@ -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(",") + "]"

View File

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

View File

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

View File

@ -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(",") + "]"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 {

View File

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

View File

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

View File

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

View File

@ -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{}

View File

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

View File

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

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

@ -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{}{

View File

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

View File

@ -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.
//

View File

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

View File

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

View File

@ -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] {

View File

@ -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...)

View File

@ -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] {

View File

@ -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 {

View File

@ -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 {

View File

@ -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(),
)
}
// 处理服务错误信息主要是panichttp请求的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)
}

View File

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

View File

@ -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

View File

@ -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!@#`)
})
}

View 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), `密码强度不足; 两次密码不一致`)
})
}

View File

@ -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
View 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
}

View File

@ -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

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

View File

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

View File

@ -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

View File

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

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

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

View File

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

View File

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

View File

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

View File

@ -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 {

View File

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

View File

@ -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 {

View File

@ -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