mirror of
https://gitee.com/johng/gf
synced 2026-06-07 02:12:11 +08:00
Merge branch 'master' into processconflict
This commit is contained in:
@ -16,7 +16,7 @@ func main() {
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
if err := conn.Send([]byte("GET / HTTP/1.1\n\n")); err != nil {
|
||||
if err := conn.Send([]byte("GET / HTTP/1.1\r\n\r\n")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@ -30,13 +30,14 @@ func main() {
|
||||
array := bytes.Split(data, []byte(": "))
|
||||
// 获得页面内容长度
|
||||
if contentLength == 0 && len(array) == 2 && bytes.EqualFold([]byte("Content-Length"), array[0]) {
|
||||
contentLength = gconv.Int(array[1])
|
||||
// http 以\r\n换行,需要把\r也去掉
|
||||
contentLength = gconv.Int(string(array[1][:len(array[1])-1]))
|
||||
}
|
||||
header = append(header, data...)
|
||||
header = append(header, '\n')
|
||||
}
|
||||
// header读取完毕,读取文本内容
|
||||
if contentLength > 0 && len(data) == 0 {
|
||||
// header读取完毕,读取文本内容, 1为\r
|
||||
if contentLength > 0 && len(data) == 1 {
|
||||
content, _ = conn.Recv(contentLength)
|
||||
break
|
||||
}
|
||||
|
||||
@ -38,10 +38,6 @@ func (c *Core) Ctx(ctx context.Context) DB {
|
||||
if ctx == nil {
|
||||
return c.db
|
||||
}
|
||||
// It is already set context in previous chaining operation.
|
||||
if c.ctx != nil {
|
||||
return c.db
|
||||
}
|
||||
ctx = context.WithValue(ctx, ctxStrictKeyName, 1)
|
||||
// It makes a shallow copy of current db and changes its context for next chaining operation.
|
||||
var (
|
||||
@ -111,6 +107,7 @@ func (c *Core) Close(ctx context.Context) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
delete(m, k)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -27,10 +27,15 @@ func (c *Core) Query(sql string, args ...interface{}) (rows *sql.Rows, err error
|
||||
func (c *Core) DoQuery(ctx context.Context, link Link, sql string, args ...interface{}) (rows *sql.Rows, err error) {
|
||||
// Transaction checks.
|
||||
if link == nil {
|
||||
if link, err = c.SlaveLink(); err != nil {
|
||||
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
|
||||
// Firstly, check and retrieve transaction link from context.
|
||||
link = &txLink{tx.tx}
|
||||
} else if link, err = c.SlaveLink(); err != nil {
|
||||
// Or else it creates one from master node.
|
||||
return nil, err
|
||||
}
|
||||
} else if !link.IsTransaction() {
|
||||
// If current link is not transaction link, it checks and retrieves transaction from context.
|
||||
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
|
||||
link = &txLink{tx.tx}
|
||||
}
|
||||
@ -84,10 +89,15 @@ func (c *Core) Exec(sql string, args ...interface{}) (result sql.Result, err err
|
||||
func (c *Core) DoExec(ctx context.Context, link Link, sql string, args ...interface{}) (result sql.Result, err error) {
|
||||
// Transaction checks.
|
||||
if link == nil {
|
||||
if link, err = c.MasterLink(); err != nil {
|
||||
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
|
||||
// Firstly, check and retrieve transaction link from context.
|
||||
link = &txLink{tx.tx}
|
||||
} else if link, err = c.MasterLink(); err != nil {
|
||||
// Or else it creates one from master node.
|
||||
return nil, err
|
||||
}
|
||||
} else if !link.IsTransaction() {
|
||||
// If current link is not transaction link, it checks and retrieves transaction from context.
|
||||
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
|
||||
link = &txLink{tx.tx}
|
||||
}
|
||||
@ -170,11 +180,25 @@ func (c *Core) Prepare(sql string, execOnMaster ...bool) (*Stmt, error) {
|
||||
|
||||
// DoPrepare calls prepare function on given link object and returns the statement object.
|
||||
func (c *Core) DoPrepare(ctx context.Context, link Link, sql string) (*Stmt, error) {
|
||||
if link != nil && !link.IsTransaction() {
|
||||
// Transaction checks.
|
||||
if link == nil {
|
||||
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
|
||||
// Firstly, check and retrieve transaction link from context.
|
||||
link = &txLink{tx.tx}
|
||||
} else {
|
||||
// Or else it creates one from master node.
|
||||
var err error
|
||||
if link, err = c.MasterLink(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else if !link.IsTransaction() {
|
||||
// If current link is not transaction link, it checks and retrieves transaction from context.
|
||||
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
|
||||
link = &txLink{tx.tx}
|
||||
}
|
||||
}
|
||||
|
||||
if c.GetConfig().PrepareTimeout > 0 {
|
||||
// DO NOT USE cancel function in prepare statement.
|
||||
ctx, _ = context.WithTimeout(ctx, c.GetConfig().PrepareTimeout)
|
||||
|
||||
@ -9,6 +9,7 @@ package gdb_test
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/os/gctx"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/database/gdb"
|
||||
@ -1116,3 +1117,34 @@ func Test_Transaction_Nested_SavePoint_RollbackTo(t *testing.T) {
|
||||
t.Assert(all[0]["id"], 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Transaction_Method(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var err error
|
||||
err = db.Transaction(gctx.New(), func(ctx context.Context, tx *gdb.TX) error {
|
||||
_, err = db.Model(table).Ctx(ctx).Data(g.Map{
|
||||
"id": 1,
|
||||
"passport": "t1",
|
||||
"password": "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname": "T1",
|
||||
"create_time": gtime.Now().String(),
|
||||
}).Insert()
|
||||
t.AssertNil(err)
|
||||
|
||||
_, err = db.Ctx(ctx).Exec(fmt.Sprintf(
|
||||
"insert into %s(`passport`,`password`,`nickname`,`create_time`,`id`) "+
|
||||
"VALUES('t2','25d55ad283aa400af464c76d713c07ad','T2','2021-08-25 21:53:00',2) ",
|
||||
table))
|
||||
t.AssertNil(err)
|
||||
return gerror.New("rollback")
|
||||
})
|
||||
t.AssertNE(err, nil)
|
||||
|
||||
count, err := db.Model(table).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 0)
|
||||
})
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ func Example_conversionNormalFormats() {
|
||||
"array" : ["John", "Ming"]
|
||||
}
|
||||
}`
|
||||
|
||||
if j, err := gjson.DecodeToJson(data); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
|
||||
@ -7,8 +7,6 @@
|
||||
// Package gcode provides universal error code definition and common error codes implements.
|
||||
package gcode
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Code is universal error code interface definition.
|
||||
type Code interface {
|
||||
// Code returns the integer number of current error code.
|
||||
@ -22,13 +20,6 @@ type Code interface {
|
||||
Detail() interface{}
|
||||
}
|
||||
|
||||
// localCode is an implementer for interface Code for internal usage only.
|
||||
type localCode struct {
|
||||
code int // Error code, usually an integer.
|
||||
message string // Brief message for this error code.
|
||||
detail interface{} // As type of interface, it is mainly designed as an extension field for error code.
|
||||
}
|
||||
|
||||
// ================================================================================================================
|
||||
// Common error code definition.
|
||||
// There are reserved internal error code by framework: code < 1000.
|
||||
@ -52,7 +43,7 @@ var (
|
||||
CodeSecurityReason = localCode{62, "Security Reason", nil} // Security Reason.
|
||||
CodeServerBusy = localCode{63, "Server Is Busy", nil} // Server is busy, please try again later.
|
||||
CodeUnknown = localCode{64, "Unknown Error", nil} // Unknown error.
|
||||
CodeResourceNotExist = localCode{65, "Resource Not Exist", nil} // Resource does not exist.
|
||||
CodeNotFound = localCode{65, "Not Found", nil} // Resource does not exist.
|
||||
CodeInvalidRequest = localCode{66, "Invalid Request", nil} // Invalid request.
|
||||
CodeBusinessValidationFailed = localCode{300, "Business Validation Failed", nil} // Business validation failed.
|
||||
)
|
||||
@ -66,30 +57,3 @@ func New(code int, message string, detail interface{}) Code {
|
||||
detail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
// Code returns the integer number of current error code.
|
||||
func (c localCode) Code() int {
|
||||
return c.code
|
||||
}
|
||||
|
||||
// Message returns the brief message for current error code.
|
||||
func (c localCode) Message() string {
|
||||
return c.message
|
||||
}
|
||||
|
||||
// Detail returns the detailed information of current error code,
|
||||
// which is mainly designed as an extension field for error code.
|
||||
func (c localCode) Detail() interface{} {
|
||||
return c.detail
|
||||
}
|
||||
|
||||
// String returns current error code as a string.
|
||||
func (c localCode) String() string {
|
||||
if c.detail != nil {
|
||||
return fmt.Sprintf(`%d:%s %v`, c.code, c.message, c.detail)
|
||||
}
|
||||
if c.message != "" {
|
||||
return fmt.Sprintf(`%d:%s`, c.code, c.message)
|
||||
}
|
||||
return fmt.Sprintf(`%d`, c.code)
|
||||
}
|
||||
|
||||
43
errors/gcode/gcode_local.go
Normal file
43
errors/gcode/gcode_local.go
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). 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 gcode
|
||||
|
||||
import "fmt"
|
||||
|
||||
// localCode is an implementer for interface Code for internal usage only.
|
||||
type localCode struct {
|
||||
code int // Error code, usually an integer.
|
||||
message string // Brief message for this error code.
|
||||
detail interface{} // As type of interface, it is mainly designed as an extension field for error code.
|
||||
}
|
||||
|
||||
// Code returns the integer number of current error code.
|
||||
func (c localCode) Code() int {
|
||||
return c.code
|
||||
}
|
||||
|
||||
// Message returns the brief message for current error code.
|
||||
func (c localCode) Message() string {
|
||||
return c.message
|
||||
}
|
||||
|
||||
// Detail returns the detailed information of current error code,
|
||||
// which is mainly designed as an extension field for error code.
|
||||
func (c localCode) Detail() interface{} {
|
||||
return c.detail
|
||||
}
|
||||
|
||||
// String returns current error code as a string.
|
||||
func (c localCode) String() string {
|
||||
if c.detail != nil {
|
||||
return fmt.Sprintf(`%d:%s %v`, c.code, c.message, c.detail)
|
||||
}
|
||||
if c.message != "" {
|
||||
return fmt.Sprintf(`%d:%s`, c.code, c.message)
|
||||
}
|
||||
return fmt.Sprintf(`%d`, c.code)
|
||||
}
|
||||
23
errors/gcode/gcode_test.go
Normal file
23
errors/gcode/gcode_test.go
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). 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 gcode_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func Test_Nil(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
c := gcode.New(1, "custom error", "detailed description")
|
||||
t.Assert(c.Code(), 1)
|
||||
t.Assert(c.Message(), "custom error")
|
||||
t.Assert(c.Detail(), "detailed description")
|
||||
})
|
||||
}
|
||||
7
go.mod
7
go.mod
@ -5,24 +5,17 @@ go 1.14
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fatih/color v1.12.0
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/gogf/mysql v1.6.1-0.20210603073548-16164ae25579
|
||||
github.com/gomodule/redigo v1.8.5
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.10 // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
go.opentelemetry.io/otel v1.0.0-RC2
|
||||
go.opentelemetry.io/otel/oteltest v1.0.0-RC2
|
||||
go.opentelemetry.io/otel/trace v1.0.0-RC2
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
|
||||
golang.org/x/text v0.3.6
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
)
|
||||
|
||||
24
go.sum
24
go.sum
@ -2,10 +2,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28 h1:LdXxtjzvZYhhUaonAaAKArG3pyC67kGL3YY+6hGG8G4=
|
||||
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
@ -20,25 +18,16 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf h1:wIOAyJMMen0ELGiFzlmqxdcV1yGbkyHBAB6PolcNbLA=
|
||||
github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
@ -55,19 +44,16 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
|
||||
@ -23,12 +23,12 @@ type (
|
||||
Server struct {
|
||||
name string // Unique name for instance management.
|
||||
config ServerConfig // Configuration.
|
||||
plugins []Plugin // Plugin array to extends server functionality.
|
||||
plugins []Plugin // Plugin array to extend server functionality.
|
||||
servers []*gracefulServer // Underlying http.Server array.
|
||||
serverCount *gtype.Int // Underlying http.Server count.
|
||||
closeChan chan struct{} // Used for underlying server closing event notification.
|
||||
serveTree map[string]interface{} // The route map tree.
|
||||
serveCache *gcache.Cache // Server cache for internal usage.
|
||||
serveCache *gcache.Cache // Server caches for internal usage.
|
||||
routesMap map[string][]registeredRouteItem // Route map mainly for route dumps and repeated route checks.
|
||||
statusHandlerMap map[string][]HandlerFunc // Custom status handler map.
|
||||
sessionManager *gsession.Manager // Session manager.
|
||||
@ -142,7 +142,7 @@ var (
|
||||
serverMapping = gmap.NewStrAnyMap(true)
|
||||
|
||||
// serverRunning marks the running server count.
|
||||
// If there no successful server running or all servers shutdown, this value is 0.
|
||||
// If there is no successful server running or all servers' shutdown, this value is 0.
|
||||
serverRunning = gtype.NewInt()
|
||||
|
||||
// wsUpGrader is the default up-grader configuration for websocket.
|
||||
|
||||
@ -33,6 +33,10 @@ type cronSchedule struct {
|
||||
const (
|
||||
// regular expression for cron pattern, which contains 6 parts of time units.
|
||||
regexForCron = `^([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,A-Za-z]+)\s+([\-/\d\*\?,A-Za-z]+)$`
|
||||
|
||||
patternItemTypeUnknown = iota
|
||||
patternItemTypeWeek
|
||||
patternItemTypeMonth
|
||||
)
|
||||
|
||||
var (
|
||||
@ -103,37 +107,37 @@ func newSchedule(pattern string) (*cronSchedule, error) {
|
||||
pattern: pattern,
|
||||
}
|
||||
// Second.
|
||||
if m, err := parseItem(match[1], 0, 59, false); err != nil {
|
||||
if m, err := parsePatternItem(match[1], 0, 59, false); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
schedule.second = m
|
||||
}
|
||||
// Minute.
|
||||
if m, err := parseItem(match[2], 0, 59, false); err != nil {
|
||||
if m, err := parsePatternItem(match[2], 0, 59, false); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
schedule.minute = m
|
||||
}
|
||||
// Hour.
|
||||
if m, err := parseItem(match[3], 0, 23, false); err != nil {
|
||||
if m, err := parsePatternItem(match[3], 0, 23, false); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
schedule.hour = m
|
||||
}
|
||||
// Day.
|
||||
if m, err := parseItem(match[4], 1, 31, true); err != nil {
|
||||
if m, err := parsePatternItem(match[4], 1, 31, true); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
schedule.day = m
|
||||
}
|
||||
// Month.
|
||||
if m, err := parseItem(match[5], 1, 12, false); err != nil {
|
||||
if m, err := parsePatternItem(match[5], 1, 12, false); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
schedule.month = m
|
||||
}
|
||||
// Week.
|
||||
if m, err := parseItem(match[6], 0, 6, true); err != nil {
|
||||
if m, err := parsePatternItem(match[6], 0, 6, true); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
schedule.week = m
|
||||
@ -144,55 +148,57 @@ func newSchedule(pattern string) (*cronSchedule, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// parseItem parses every item in the pattern and returns the result as map.
|
||||
func parseItem(item string, min int, max int, allowQuestionMark bool) (map[int]struct{}, error) {
|
||||
// parsePatternItem parses every item in the pattern and returns the result as map, which is used for indexing.
|
||||
func parsePatternItem(item string, min int, max int, allowQuestionMark bool) (map[int]struct{}, error) {
|
||||
m := make(map[int]struct{}, max-min+1)
|
||||
if item == "*" || (allowQuestionMark && item == "?") {
|
||||
for i := min; i <= max; i++ {
|
||||
m[i] = struct{}{}
|
||||
}
|
||||
} else {
|
||||
// Like: MON,FRI
|
||||
for _, item := range strings.Split(item, ",") {
|
||||
interval := 1
|
||||
intervalArray := strings.Split(item, "/")
|
||||
var (
|
||||
interval = 1
|
||||
intervalArray = strings.Split(item, "/")
|
||||
)
|
||||
if len(intervalArray) == 2 {
|
||||
if i, err := strconv.Atoi(intervalArray[1]); err != nil {
|
||||
if number, err := strconv.Atoi(intervalArray[1]); err != nil {
|
||||
return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid pattern item: "%s"`, item)
|
||||
} else {
|
||||
interval = i
|
||||
interval = number
|
||||
}
|
||||
}
|
||||
var (
|
||||
rangeMin = min
|
||||
rangeMax = max
|
||||
fieldType = byte(0)
|
||||
itemType = patternItemTypeUnknown
|
||||
rangeArray = strings.Split(intervalArray[0], "-") // Like: 1-30, JAN-DEC
|
||||
)
|
||||
switch max {
|
||||
case 6:
|
||||
// It's checking week field.
|
||||
fieldType = 'w'
|
||||
itemType = patternItemTypeWeek
|
||||
case 12:
|
||||
// It's checking month field.
|
||||
fieldType = 'm'
|
||||
itemType = patternItemTypeMonth
|
||||
}
|
||||
// Eg: */5
|
||||
if rangeArray[0] != "*" {
|
||||
if i, err := parseItemValue(rangeArray[0], fieldType); err != nil {
|
||||
if number, err := parsePatternItemValue(rangeArray[0], itemType); err != nil {
|
||||
return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid pattern item: "%s"`, item)
|
||||
} else {
|
||||
rangeMin = i
|
||||
rangeMin = number
|
||||
if len(intervalArray) == 1 {
|
||||
rangeMax = i
|
||||
rangeMax = number
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(rangeArray) == 2 {
|
||||
if i, err := parseItemValue(rangeArray[1], fieldType); err != nil {
|
||||
if number, err := parsePatternItemValue(rangeArray[1], itemType); err != nil {
|
||||
return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid pattern item: "%s"`, item)
|
||||
} else {
|
||||
rangeMax = i
|
||||
|
||||
rangeMax = number
|
||||
}
|
||||
}
|
||||
for i := rangeMin; i <= rangeMax; i += interval {
|
||||
@ -203,24 +209,24 @@ func parseItem(item string, min int, max int, allowQuestionMark bool) (map[int]s
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// parseItemValue parses the field value to a number according to its field type.
|
||||
func parseItemValue(value string, fieldType byte) (int, error) {
|
||||
// parsePatternItemValue parses the field value to a number according to its field type.
|
||||
func parsePatternItemValue(value string, itemType int) (int, error) {
|
||||
if gregex.IsMatchString(`^\d+$`, value) {
|
||||
// Pure number.
|
||||
if i, err := strconv.Atoi(value); err == nil {
|
||||
return i, nil
|
||||
// It is pure number.
|
||||
if number, err := strconv.Atoi(value); err == nil {
|
||||
return number, nil
|
||||
}
|
||||
} else {
|
||||
// Check if contains letter,
|
||||
// Check if it contains letter,
|
||||
// it converts the value to number according to predefined map.
|
||||
switch fieldType {
|
||||
case 'm':
|
||||
if i, ok := monthMap[strings.ToLower(value)]; ok {
|
||||
return i, nil
|
||||
switch itemType {
|
||||
case patternItemTypeWeek:
|
||||
if number, ok := monthMap[strings.ToLower(value)]; ok {
|
||||
return number, nil
|
||||
}
|
||||
case 'w':
|
||||
if i, ok := weekMap[strings.ToLower(value)]; ok {
|
||||
return i, nil
|
||||
case patternItemTypeMonth:
|
||||
if number, ok := weekMap[strings.ToLower(value)]; ok {
|
||||
return number, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ func ParseContent(ctx context.Context, content string, params ...Params) (string
|
||||
}
|
||||
|
||||
// New returns a new view object.
|
||||
// The parameter <path> specifies the template directory path to load template files.
|
||||
// The parameter `path` specifies the template directory path to load template files.
|
||||
func New(path ...string) *View {
|
||||
view := &View{
|
||||
paths: garray.NewStrArray(),
|
||||
@ -143,6 +143,10 @@ func New(path ...string) *View {
|
||||
"map": view.buildInFuncMap,
|
||||
"maps": view.buildInFuncMaps,
|
||||
"json": view.buildInFuncJson,
|
||||
"plus": view.buildInFuncPlus,
|
||||
"minus": view.buildInFuncMinus,
|
||||
"times": view.buildInFuncTimes,
|
||||
"divide": view.buildInFuncDivide,
|
||||
})
|
||||
|
||||
return view
|
||||
|
||||
@ -219,8 +219,53 @@ func (view *View) buildInFuncNl2Br(str interface{}) string {
|
||||
}
|
||||
|
||||
// buildInFuncJson implements build-in template function: json ,
|
||||
// which encodes and returns <value> as JSON string.
|
||||
// which encodes and returns `value` as JSON string.
|
||||
func (view *View) buildInFuncJson(value interface{}) (string, error) {
|
||||
b, err := json.Marshal(value)
|
||||
return gconv.UnsafeBytesToStr(b), err
|
||||
}
|
||||
|
||||
// buildInFuncPlus implements build-in template function: plus ,
|
||||
// which returns the result that pluses all `deltas` to `value`.
|
||||
func (view *View) buildInFuncPlus(value interface{}, deltas ...interface{}) string {
|
||||
result := gconv.Float64(value)
|
||||
for _, v := range deltas {
|
||||
result += gconv.Float64(v)
|
||||
}
|
||||
return gconv.String(result)
|
||||
}
|
||||
|
||||
// buildInFuncMinus implements build-in template function: minus ,
|
||||
// which returns the result that subtracts all `deltas` from `value`.
|
||||
func (view *View) buildInFuncMinus(value interface{}, deltas ...interface{}) string {
|
||||
result := gconv.Float64(value)
|
||||
for _, v := range deltas {
|
||||
result -= gconv.Float64(v)
|
||||
}
|
||||
return gconv.String(result)
|
||||
}
|
||||
|
||||
// buildInFuncTimes implements build-in template function: times ,
|
||||
// which returns the result that multiplies `value` by all of `values`.
|
||||
func (view *View) buildInFuncTimes(value interface{}, values ...interface{}) string {
|
||||
result := gconv.Float64(value)
|
||||
for _, v := range values {
|
||||
result *= gconv.Float64(v)
|
||||
}
|
||||
return gconv.String(result)
|
||||
}
|
||||
|
||||
// buildInFuncDivide implements build-in template function: divide ,
|
||||
// which returns the result that divides `value` by all of `values`.
|
||||
func (view *View) buildInFuncDivide(value interface{}, values ...interface{}) string {
|
||||
result := gconv.Float64(value)
|
||||
for _, v := range values {
|
||||
value2Float64 := gconv.Float64(v)
|
||||
if value2Float64 == 0 {
|
||||
// Invalid `value2`.
|
||||
return "0"
|
||||
}
|
||||
result /= value2Float64
|
||||
}
|
||||
return gconv.String(result)
|
||||
}
|
||||
|
||||
@ -95,7 +95,7 @@ func (view *View) SetConfigWithMap(m map[string]interface{}) error {
|
||||
}
|
||||
|
||||
// SetPath sets the template directory path for template file search.
|
||||
// The parameter <path> can be absolute or relative path, but absolute path is suggested.
|
||||
// The parameter `path` can be absolute or relative path, but absolute path is suggested.
|
||||
func (view *View) SetPath(path string) error {
|
||||
var (
|
||||
isDir = false
|
||||
@ -239,9 +239,9 @@ 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.
|
||||
// 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.
|
||||
func (view *View) BindFunc(name string, function interface{}) {
|
||||
view.funcMap[name] = function
|
||||
// Clear global template object cache.
|
||||
|
||||
@ -19,7 +19,7 @@ var (
|
||||
)
|
||||
|
||||
// Instance returns an instance of View with default settings.
|
||||
// The parameter <name> is the name for the instance.
|
||||
// The parameter `name` is the name for the instance.
|
||||
func Instance(name ...string) *View {
|
||||
key := DefaultName
|
||||
if len(name) > 0 && name[0] != "" {
|
||||
|
||||
@ -53,7 +53,7 @@ var (
|
||||
resourceTryFolders = []string{"template/", "template", "/template", "/template/"}
|
||||
)
|
||||
|
||||
// Parse parses given template file <file> with given template variables <params>
|
||||
// Parse parses given template file `file` with given template variables `params`
|
||||
// and returns the parsed template content.
|
||||
func (view *View) Parse(ctx context.Context, file string, params ...Params) (result string, err error) {
|
||||
var tpl interface{}
|
||||
@ -65,7 +65,7 @@ func (view *View) Parse(ctx context.Context, file string, params ...Params) (res
|
||||
content string
|
||||
resource *gres.File
|
||||
)
|
||||
// Searching the absolute file path for <file>.
|
||||
// Searching the absolute file path for `file`.
|
||||
path, folder, resource, err = view.searchFile(file)
|
||||
if err != nil {
|
||||
return nil
|
||||
@ -100,7 +100,7 @@ func (view *View) Parse(ctx context.Context, file string, params ...Params) (res
|
||||
if item.content == "" {
|
||||
return "", nil
|
||||
}
|
||||
// Get the template object instance for <folder>.
|
||||
// Get the template object instance for `folder`.
|
||||
tpl, err = view.getTemplate(item.path, item.folder, fmt.Sprintf(`*%s`, gfile.Ext(item.path)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -120,7 +120,7 @@ func (view *View) Parse(ctx context.Context, file string, params ...Params) (res
|
||||
return "", err
|
||||
}
|
||||
// Note that the template variable assignment cannot change the value
|
||||
// of the existing <params> or view.data because both variables are pointers.
|
||||
// of the existing `params` or view.data because both variables are pointers.
|
||||
// It needs to merge the values of the two maps into a new map.
|
||||
variables := gutil.MapMergeCopy(params...)
|
||||
if len(view.data) > 0 {
|
||||
@ -154,7 +154,7 @@ func (view *View) ParseDefault(ctx context.Context, params ...Params) (result st
|
||||
return view.Parse(ctx, view.config.DefaultFile, params...)
|
||||
}
|
||||
|
||||
// ParseContent parses given template content <content> with template variables <params>
|
||||
// ParseContent parses given template content `content` with template variables `params`
|
||||
// and returns the parsed content in []byte.
|
||||
func (view *View) ParseContent(ctx context.Context, content string, params ...Params) (string, error) {
|
||||
// It's not necessary continuing parsing if template content is empty.
|
||||
@ -188,7 +188,7 @@ func (view *View) ParseContent(ctx context.Context, content string, params ...Pa
|
||||
return "", err
|
||||
}
|
||||
// Note that the template variable assignment cannot change the value
|
||||
// of the existing <params> or view.data because both variables are pointers.
|
||||
// of the existing `params` or view.data because both variables are pointers.
|
||||
// It needs to merge the values of the two maps into a new map.
|
||||
variables := gutil.MapMergeCopy(params...)
|
||||
if len(view.data) > 0 {
|
||||
@ -216,10 +216,10 @@ func (view *View) ParseContent(ctx context.Context, content string, params ...Pa
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// getTemplate returns the template object associated with given template file <path>.
|
||||
// getTemplate returns the template object associated with given template file `path`.
|
||||
// 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).
|
||||
// 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 interface{}, err error) {
|
||||
// Key for template cache.
|
||||
key := fmt.Sprintf("%s_%v", filePath, view.config.Delimiters)
|
||||
@ -304,9 +304,9 @@ func (view *View) formatTemplateObjectCreatingError(filePath, tplName string, er
|
||||
return nil
|
||||
}
|
||||
|
||||
// searchFile returns the found absolute path for <file> and its template folder path.
|
||||
// Note that, the returned <folder> is the template folder path, but not the folder of
|
||||
// the returned template file <path>.
|
||||
// searchFile returns the found absolute path for `file` and its template folder path.
|
||||
// Note that, the returned `folder` is the template folder path, but not the folder of
|
||||
// the returned template file `path`.
|
||||
func (view *View) searchFile(file string) (path string, folder string, resource *gres.File, err error) {
|
||||
// Firstly checking the resource manager.
|
||||
if !gres.IsEmpty() {
|
||||
|
||||
@ -9,6 +9,7 @@ package gview_test
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/encoding/ghtml"
|
||||
"github.com/gogf/gf/os/gctx"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"io/ioutil"
|
||||
@ -426,3 +427,39 @@ func Test_BuildInFuncJson(t *testing.T) {
|
||||
t.Assert(r, `{"name":"john"}`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_BuildInFuncPlus(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v := gview.New()
|
||||
r, err := v.ParseContent(gctx.New(), "{{plus 1 2 3}}")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(r, `6`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_BuildInFuncMinus(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v := gview.New()
|
||||
r, err := v.ParseContent(gctx.New(), "{{minus 1 2 3}}")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(r, `-4`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_BuildInFuncTimes(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v := gview.New()
|
||||
r, err := v.ParseContent(gctx.New(), "{{times 1 2 3 4}}")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(r, `24`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_BuildInFuncDivide(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v := gview.New()
|
||||
r, err := v.ParseContent(gctx.New(), "{{divide 8 2 2}}")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(r, `2`)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user