Compare commits

...

32 Commits

Author SHA1 Message Date
1d53d760d8 README/DONATOR update; improve ghttp 2019-12-19 15:38:34 +08:00
922e720d63 improve gdb/ghttp/gins; fix issue in gstr 2019-12-19 15:14:05 +08:00
50018773b7 add iterate example for glist; improve variable name for ghttp.Server 2019-12-18 19:45:46 +08:00
df99036d41 add iterate example for glist; improve variable name for ghttp.Server 2019-12-18 19:44:40 +08:00
ae0fa888f0 add iterate example for glist; improve variable name for ghttp.Server 2019-12-18 19:40:07 +08:00
18892fb66d add iterate example for glist; improve variable name for ghttp.Server 2019-12-18 19:37:07 +08:00
5f2be10563 improve gdb/gtime 2019-12-17 21:06:34 +08:00
a5a88222a6 gofmt 2019-12-16 22:51:17 +08:00
4facdd5c9e gofmt 2019-12-16 21:22:42 +08:00
76bc9bd385 improve gdb.Model 2019-12-16 21:00:16 +08:00
364452f3bb improve gdb.Model 2019-12-16 20:50:27 +08:00
4996755f11 improve gcmd.Scan* 2019-12-14 22:48:53 +08:00
e33230a88f add package gbuild 2019-12-14 17:01:27 +08:00
951ce46932 fix data race issue in unit testing cases for gtcp 2019-12-13 18:50:54 +08:00
795c7395e6 fix data race issue in unit testing cases for gtcp 2019-12-13 17:40:29 +08:00
27cf47bcd3 improve table prefix and quote feature for gdb 2019-12-13 15:25:49 +08:00
58a25c6f61 fix data race issue in unit testing of gtcp 2019-12-13 14:26:07 +08:00
2d754f80b1 fix data race issue in unit testing of gtcp 2019-12-13 13:43:28 +08:00
f4e8fbe767 fix issue in unit testing codes for gtcp 2019-12-13 08:57:39 +08:00
458318d374 improve prefix and word quote feature for gdb; merge develop 2019-12-12 23:54:07 +08:00
e3f54e1353 improve tag and field retrieving feature for internal/structs; comment update for gdb 2019-12-12 23:38:46 +08:00
4374996073 add some Must* functions for gparser/gjson/gmd5/gsha1/gbase64 2019-12-12 11:40:23 +08:00
87295ef1fe improve process communication feature for gproc; add more slice converting functions for gconv 2019-12-11 21:22:41 +08:00
81d4082b6a fix issue in Retry.Interval type changed for gtcp 2019-12-11 14:54:38 +08:00
d7e19bc3f3 improve package feature for gtcp 2019-12-11 14:50:25 +08:00
34ef0ea792 add prefix feature for gdb 2019-12-10 21:14:15 +08:00
2804834540 rename GetRouterMap to GetRouterArray for ghttp.Server 2019-12-10 12:28:55 +08:00
add7dd5a45 merge develop 2019-12-09 23:22:49 +08:00
e40894ca45 add IsServiceHandler for ghttp.RouterItem 2019-12-09 23:19:39 +08:00
28825f5395 add gdebug.BuildInfo function; improving gproc 2019-12-09 21:53:44 +08:00
6ca5141020 some improving 2019-12-08 22:55:32 +08:00
fe4f8e1810 version updates 2019-12-05 19:46:50 +08:00
117 changed files with 5100 additions and 1967 deletions

View File

@ -10,10 +10,10 @@ func main() {
// Push
l.PushBack(1)
l.PushBack(2)
e0 := l.PushFront(0)
e := l.PushFront(0)
// Insert
l.InsertBefore(e0, -1)
l.InsertAfter(e0, "a")
l.InsertBefore(e, -1)
l.InsertAfter(e, "a")
fmt.Println(l)
// Pop
fmt.Println(l.PopFront())

View File

@ -0,0 +1,10 @@
package main
import (
"fmt"
"github.com/gogf/gf/debug/gdebug"
)
func main() {
fmt.Println(gdebug.BuildInfo())
}

View File

@ -0,0 +1,3 @@
package article
// Fill with you ideas below.

View File

@ -0,0 +1,68 @@
// ==========================================================================
// This is auto-generated by gf cli tool. You may not really want to edit it.
// ==========================================================================
package article
import (
"database/sql"
"github.com/gogf/gf/database/gdb"
"github.com/gogf/gf/os/gtime"
)
// Entity is the golang structure for table gf_article.
type Entity struct {
Id int `orm:"id,primary" json:"id"` //
CatId int `orm:"cat_id" json:"cat_id"` // 分类ID
Uid int `orm:"uid" json:"uid"` // 用户ID
Title string `orm:"title" json:"title"` // 标题
Content string `orm:"content" json:"content"` // 内容
Order int `orm:"order" json:"order"` // 排序
Brief string `orm:"brief" json:"brief"` // 摘要
Thumb string `orm:"thumb" json:"thumb"` // 缩略图
Tags string `orm:"tags" json:"tags"` // 标签
Referer string `orm:"referer" json:"referer"` // 内容来源
Status int `orm:"status" json:"status"` // 状态\n0: 禁用\n1: 正常
CreateTime *gtime.Time `orm:"create_time" json:"create_time"` // 创建时间
UpdateTime *gtime.Time `orm:"update_time" json:"update_time"` // 修改时间
}
// Article is alias of Entity, which some developers say they just want.
type Article = Entity
// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
// the data and where attributes for empty values.
func (r *Entity) OmitEmpty() *arModel {
return Model.Data(r).OmitEmpty()
}
// Inserts does "INSERT...INTO..." statement for inserting current object into table.
func (r *Entity) Insert() (result sql.Result, err error) {
return Model.Data(r).Insert()
}
// Replace does "REPLACE...INTO..." statement for inserting current object into table.
// If there's already another same record in the table (it checks using primary key or unique index),
// it deletes it and insert this one.
func (r *Entity) Replace() (result sql.Result, err error) {
return Model.Data(r).Replace()
}
// Save does "INSERT...INTO..." statement for inserting/updating current object into table.
// It updates the record if there's already another same record in the table
// (it checks using primary key or unique index).
func (r *Entity) Save() (result sql.Result, err error) {
return Model.Data(r).Save()
}
// Update does "UPDATE...WHERE..." statement for updating current object from table.
// It updates the record if there's already another same record in the table
// (it checks using primary key or unique index).
func (r *Entity) Update() (result sql.Result, err error) {
return Model.Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update()
}
// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table.
func (r *Entity) Delete() (result sql.Result, err error) {
return Model.Where(gdb.GetWhereConditionOfStruct(r)).Delete()
}

View File

@ -0,0 +1,362 @@
// ==========================================================================
// This is auto-generated by gf cli tool. You may not really want to edit it.
// ==========================================================================
package article
import (
"database/sql"
"github.com/gogf/gf/database/gdb"
"github.com/gogf/gf/frame/g"
"time"
)
// arModel is a active record design model for table gf_article operations.
type arModel struct {
M *gdb.Model
}
var (
// Table is the table name of gf_article.
Table = "gf_article"
// Model is the model object of gf_article.
Model = &arModel{g.DB("default").Table(Table).Safe()}
)
// FindOne is a convenience method for Model.FindOne.
// See Model.FindOne.
func FindOne(where ...interface{}) (*Entity, error) {
return Model.FindOne(where...)
}
// FindAll is a convenience method for Model.FindAll.
// See Model.FindAll.
func FindAll(where ...interface{}) ([]*Entity, error) {
return Model.FindAll(where...)
}
// FindValue is a convenience method for Model.FindValue.
// See Model.FindValue.
func FindValue(fieldsAndWhere ...interface{}) (gdb.Value, error) {
return Model.FindValue(fieldsAndWhere...)
}
// FindCount is a convenience method for Model.FindCount.
// See Model.FindCount.
func FindCount(where ...interface{}) (int, error) {
return Model.FindCount(where...)
}
// Insert is a convenience method for Model.Insert.
func Insert(data ...interface{}) (result sql.Result, err error) {
return Model.Insert(data...)
}
// Replace is a convenience method for Model.Replace.
func Replace(data ...interface{}) (result sql.Result, err error) {
return Model.Replace(data...)
}
// Save is a convenience method for Model.Save.
func Save(data ...interface{}) (result sql.Result, err error) {
return Model.Save(data...)
}
// Update is a convenience method for Model.Update.
func Update(dataAndWhere ...interface{}) (result sql.Result, err error) {
return Model.Update(dataAndWhere...)
}
// Delete is a convenience method for Model.Delete.
func Delete(where ...interface{}) (result sql.Result, err error) {
return Model.Delete(where...)
}
// TX sets the transaction for current operation.
func (m *arModel) TX(tx *gdb.TX) *arModel {
return &arModel{m.M.TX(tx)}
}
// Master marks the following operation on master node.
func (m *arModel) Master() *arModel {
return &arModel{m.M.Master()}
}
// Slave marks the following operation on slave node.
// Note that it makes sense only if there's any slave node configured.
func (m *arModel) Slave() *arModel {
return &arModel{m.M.Slave()}
}
// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
func (m *arModel) LeftJoin(joinTable string, on string) *arModel {
return &arModel{m.M.LeftJoin(joinTable, on)}
}
// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
func (m *arModel) RightJoin(joinTable string, on string) *arModel {
return &arModel{m.M.RightJoin(joinTable, on)}
}
// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
func (m *arModel) InnerJoin(joinTable string, on string) *arModel {
return &arModel{m.M.InnerJoin(joinTable, on)}
}
// Fields sets the operation fields of the model, multiple fields joined using char ','.
func (m *arModel) Fields(fields string) *arModel {
return &arModel{m.M.Fields(fields)}
}
// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
func (m *arModel) FieldsEx(fields string) *arModel {
return &arModel{m.M.FieldsEx(fields)}
}
// Option sets the extra operation option for the model.
func (m *arModel) Option(option int) *arModel {
return &arModel{m.M.Option(option)}
}
// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
// the data and where attributes for empty values.
func (m *arModel) OmitEmpty() *arModel {
return &arModel{m.M.OmitEmpty()}
}
// Filter marks filtering the fields which does not exist in the fields of the operated table.
func (m *arModel) Filter() *arModel {
return &arModel{m.M.Filter()}
}
// Where sets the condition statement for the model. The parameter <where> can be type of
// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
// multiple conditions will be joined into where statement using "AND".
// Eg:
// Where("uid=10000")
// Where("uid", 10000)
// Where("money>? AND name like ?", 99999, "vip_%")
// Where("uid", 1).Where("name", "john")
// Where("status IN (?)", g.Slice{1,2,3})
// Where("age IN(?,?)", 18, 50)
// Where(User{ Id : 1, UserName : "john"})
func (m *arModel) Where(where interface{}, args ...interface{}) *arModel {
return &arModel{m.M.Where(where, args...)}
}
// And adds "AND" condition to the where statement.
func (m *arModel) And(where interface{}, args ...interface{}) *arModel {
return &arModel{m.M.And(where, args...)}
}
// Or adds "OR" condition to the where statement.
func (m *arModel) Or(where interface{}, args ...interface{}) *arModel {
return &arModel{m.M.Or(where, args...)}
}
// Group sets the "GROUP BY" statement for the model.
func (m *arModel) Group(groupBy string) *arModel {
return &arModel{m.M.Group(groupBy)}
}
// Order sets the "ORDER BY" statement for the model.
func (m *arModel) Order(orderBy string) *arModel {
return &arModel{m.M.Order(orderBy)}
}
// Limit sets the "LIMIT" statement for the model.
// The parameter <limit> can be either one or two number, if passed two number is passed,
// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]"
// statement.
func (m *arModel) Limit(limit ...int) *arModel {
return &arModel{m.M.Limit(limit...)}
}
// Offset sets the "OFFSET" statement for the model.
// It only makes sense for some databases like SQLServer, PostgreSQL, etc.
func (m *arModel) Offset(offset int) *arModel {
return &arModel{m.M.Offset(offset)}
}
// Page sets the paging number for the model.
// The parameter <page> is started from 1 for paging.
// Note that, it differs that the Limit function start from 0 for "LIMIT" statement.
func (m *arModel) Page(page, limit int) *arModel {
return &arModel{m.M.Page(page, limit)}
}
// Batch sets the batch operation number for the model.
func (m *arModel) Batch(batch int) *arModel {
return &arModel{m.M.Batch(batch)}
}
// Cache sets the cache feature for the model. It caches the result of the sql, which means
// if there's another same sql request, it just reads and returns the result from cache, it
// but not committed and executed into the database.
//
// If the parameter <duration> < 0, which means it clear the cache with given <name>.
// If the parameter <duration> = 0, which means it never expires.
// If the parameter <duration> > 0, which means it expires after <duration>.
//
// The optional parameter <name> is used to bind a name to the cache, which means you can later
// control the cache like changing the <duration> or clearing the cache with specified <name>.
//
// Note that, the cache feature is disabled if the model is operating on a transaction.
func (m *arModel) Cache(expire time.Duration, name ...string) *arModel {
return &arModel{m.M.Cache(expire, name...)}
}
// Data sets the operation data for the model.
// The parameter <data> can be type of string/map/gmap/slice/struct/*struct, etc.
// Eg:
// Data("uid=10000")
// Data("uid", 10000)
// Data(g.Map{"uid": 10000, "name":"john"})
// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
func (m *arModel) Data(data ...interface{}) *arModel {
return &arModel{m.M.Data(data...)}
}
// Insert does "INSERT INTO ..." statement for the model.
// The optional parameter <data> is the same as the parameter of Model.Data function,
// see Model.Data.
func (m *arModel) Insert(data ...interface{}) (result sql.Result, err error) {
return m.M.Insert(data...)
}
// Replace does "REPLACE INTO ..." statement for the model.
// The optional parameter <data> is the same as the parameter of Model.Data function,
// see Model.Data.
func (m *arModel) Replace(data ...interface{}) (result sql.Result, err error) {
return m.M.Replace(data...)
}
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the model.
// It updates the record if there's primary or unique index in the saving data,
// or else it inserts a new record into the table.
//
// The optional parameter <data> is the same as the parameter of Model.Data function,
// see Model.Data.
func (m *arModel) Save(data ...interface{}) (result sql.Result, err error) {
return m.M.Save(data...)
}
// Update does "UPDATE ... " statement for the model.
//
// If the optional parameter <dataAndWhere> is given, the dataAndWhere[0] is the updated
// data field, and dataAndWhere[1:] is treated as where condition fields.
// Also see Model.Data and Model.Where functions.
func (m *arModel) Update(dataAndWhere ...interface{}) (result sql.Result, err error) {
return m.M.Update(dataAndWhere...)
}
// Delete does "DELETE FROM ... " statement for the model.
// The optional parameter <where> is the same as the parameter of Model.Where function,
// see Model.Where.
func (m *arModel) Delete(where ...interface{}) (result sql.Result, err error) {
return m.M.Delete(where...)
}
// Count does "SELECT COUNT(x) FROM ..." statement for the model.
// The optional parameter <where> is the same as the parameter of Model.Where function,
// see Model.Where.
func (m *arModel) Count(where ...interface{}) (int, error) {
return m.M.Count(where...)
}
// All does "SELECT FROM ..." statement for the model.
// It retrieves the records from table and returns the result as []*Entity.
// It returns nil if there's no record retrieved with the given conditions from table.
//
// The optional parameter <where> is the same as the parameter of Model.Where function,
// see Model.Where.
func (m *arModel) All(where ...interface{}) ([]*Entity, error) {
all, err := m.M.All(where...)
if err != nil {
return nil, err
}
var entities []*Entity
if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
return nil, err
}
return entities, nil
}
// One retrieves one record from table and returns the result as *Entity.
// It returns nil if there's no record retrieved with the given conditions from table.
//
// The optional parameter <where> is the same as the parameter of Model.Where function,
// see Model.Where.
func (m *arModel) One(where ...interface{}) (*Entity, error) {
one, err := m.M.One(where...)
if err != nil {
return nil, err
}
var entity *Entity
if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
return nil, err
}
return entity, nil
}
// Value retrieves a specified record value from table and returns the result as interface type.
// It returns nil if there's no record found with the given conditions from table.
//
// If the optional parameter <fieldsAndWhere> is given, the fieldsAndWhere[0] is the selected fields
// and fieldsAndWhere[1:] is treated as where condition fields.
// Also see Model.Fields and Model.Where functions.
func (m *arModel) Value(fieldsAndWhere ...interface{}) (gdb.Value, error) {
return m.M.Value(fieldsAndWhere...)
}
// FindOne retrieves and returns a single Record by Model.WherePri and Model.One.
// Also see Model.WherePri and Model.One.
func (m *arModel) FindOne(where ...interface{}) (*Entity, error) {
one, err := m.M.FindOne(where...)
if err != nil {
return nil, err
}
var entity *Entity
if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
return nil, err
}
return entity, nil
}
// FindAll retrieves and returns Result by by Model.WherePri and Model.All.
// Also see Model.WherePri and Model.All.
func (m *arModel) FindAll(where ...interface{}) ([]*Entity, error) {
all, err := m.M.FindAll(where...)
if err != nil {
return nil, err
}
var entities []*Entity
if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
return nil, err
}
return entities, nil
}
// FindValue retrieves and returns single field value by Model.WherePri and Model.Value.
// Also see Model.WherePri and Model.Value.
func (m *arModel) FindValue(fieldsAndWhere ...interface{}) (gdb.Value, error) {
return m.M.FindValue(fieldsAndWhere...)
}
// FindCount retrieves and returns the record number by Model.WherePri and Model.Count.
// Also see Model.WherePri and Model.Count.
func (m *arModel) FindCount(where ...interface{}) (int, error) {
return m.M.FindCount(where...)
}
// Chunk iterates the table with given size and callback function.
func (m *arModel) Chunk(limit int, callback func(entities []*Entity, err error) bool) {
m.M.Chunk(limit, func(result gdb.Result, err error) bool {
var entities []*Entity
err = result.Structs(&entities)
if err == sql.ErrNoRows {
return false
}
return callback(entities, err)
})
}

View File

@ -1,60 +0,0 @@
// This is auto-generated by gf cli tool. You may not really want to edit it.
package defaults
import (
"database/sql"
"github.com/gogf/gf/database/gdb"
"github.com/gogf/gf/frame/g"
)
import (
"github.com/gogf/gf/os/gtime"
)
// User is the golang structure for table user.
type User struct {
Id int `orm:"id,primary" json:"id"`
Passport string `orm:"passport" json:"passport"`
Password string `orm:"password" json:"password"`
Nickname string `orm:"nickname,unique" json:"nickname"`
CreateTime *gtime.Time `orm:"create_time" json:"create_time"`
}
var (
// TableUser is the table name of user.
TableUser = "user"
// ModelUser is the model object of user.
ModelUser = g.DB("default").Table(TableUser).Safe()
)
// Inserts does "INSERT...INTO..." statement for inserting current object into table.
func (r *User) Insert() (result sql.Result, err error) {
return ModelUser.Data(r).Insert()
}
// Replace does "REPLACE...INTO..." statement for inserting current object into table.
// If there's already another same record in the table (it checks using primary key or unique index),
// it deletes it and insert this one.
func (r *User) Replace() (result sql.Result, err error) {
return ModelUser.Data(r).Replace()
}
// Save does "INSERT...INTO..." statement for inserting/updating current object into table.
// It updates the record if there's already another same record in the table
// (it checks using primary key or unique index).
func (r *User) Save() (result sql.Result, err error) {
return ModelUser.Data(r).Save()
}
// Update does "UPDATE...WHERE..." statement for updating current object from table.
// It updates the record if there's already another same record in the table
// (it checks using primary key or unique index).
func (r *User) Update() (result sql.Result, err error) {
return ModelUser.Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update()
}
// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table.
func (r *User) Delete() (result sql.Result, err error) {
return ModelUser.Where(gdb.GetWhereConditionOfStruct(r)).Delete()
}

View File

@ -3,7 +3,7 @@ viewpath = "/home/www/templates"
# MySQL数据库配置
[database]
debug = true
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/gf"
[redis]

View File

@ -1,13 +1,10 @@
package main
import (
"fmt"
"github.com/gogf/gf/.example/frame/mvc/app/model/defaults"
"github.com/gogf/gf/database/gdb"
"github.com/gogf/gf/.example/frame/mvc/app/model/article"
)
func main() {
u := defaults.User{Id: 1, Nickname: "test"}
fmt.Println(gdb.GetWhereConditionOfStruct(&u))
fmt.Println(u.Replace())
m := article.Model
m.All()
}

View File

@ -0,0 +1,9 @@
# custom gf build setting.
[compiler]
name = "app"
[compiler.varmap]
name = "GoFrame"
version = "1.10.1"
home-site = "https://goframe.org"

View File

@ -0,0 +1,11 @@
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/os/gbuild"
)
func main() {
g.Dump(gbuild.Info())
g.Dump(gbuild.Map())
}

View File

@ -1,6 +1,3 @@
// 多进程通信示例,
// 子进程每个1秒向父进程发送当前时间
// 父进程监听进程消息,收到后打印到终端。
package main
import (

View File

@ -2,16 +2,15 @@ package main
import (
"fmt"
"github.com/gogf/gf/os/gproc"
)
// 使用gproc kill指定其他进程(清确保运行该程序的用户有足够权限)
func main() {
pid := 28536
pid := 32556
m := gproc.NewManager()
m.AddProcess(pid)
m.KillAll()
err := m.KillAll()
fmt.Println(err)
m.WaitAll()
fmt.Printf("%d was killed\n", pid)
}

View File

@ -0,0 +1,12 @@
package main
import (
"fmt"
"github.com/gogf/gf/os/gproc"
)
func main() {
fmt.Println(gproc.Pid())
err := gproc.ShellRun("sleep 99999s")
fmt.Println(err)
}

View File

@ -1,3 +1,5 @@
[database]
debug = true
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true&loc=Local"
[redis]
default = "127.0.0.1:6379,0"
cache = "127.0.0.1:6379,1"

View File

@ -1,42 +0,0 @@
package main
import (
"fmt"
)
func main() {
fmt.Println("")
// 前景 背景 颜色
// ---------------------------------------
// 30 40 黑色
// 31 41 红色
// 32 42 绿色
// 33 43 黄色
// 34 44 蓝色
// 35 45 紫红色
// 36 46 青蓝色
// 37 47 白色
//
// 代码 意义
// -------------------------
// 0 终端默认设置
// 1 高亮显示
// 4 使用下划线
// 5 闪烁
// 7 反白显示
// 8 不可见
// 背景色彩 = 40-47
for b := 40; b <= 47; b++ {
// 前景色彩 = 30-37
for f := 30; f <= 37; f++ {
// 显示方式 = 0,1,4,5,7,8
for _, d := range []int{0, 1, 4, 5, 7, 8} {
fmt.Printf(" %c[%d;%d;%dm%s(f=%d,b=%d,d=%d)%c[0m ", 0x1B, d, b, f, "", f, b, d, 0x1B)
}
fmt.Println("")
}
fmt.Println("")
}
}

View File

@ -1,17 +1,13 @@
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"fmt"
"github.com/gogf/gf/text/gstr"
)
func main() {
s := g.Server()
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
group.ALL("/test", func(r *ghttp.Request) {
r.Response.Write(r.GetRequest("nickname"))
})
})
s.SetPort(8199)
s.Run()
a := "aaaaa_post"
b := "aaaaa_"
c := gstr.TrimLeftStr(a, b)
fmt.Println(c)
}

View File

@ -1,13 +0,0 @@
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/text/gregex"
)
func main() {
s := `-abc`
m, err := gregex.MatchString(`^\-{1,2}a={0,1}(.*)`, s)
g.Dump(err)
g.Dump(m)
}

View File

@ -1,22 +0,0 @@
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/os/glog"
)
func main() {
g.TryCatch(func() {
glog.Println("hello")
g.Throw("exception")
glog.Println("world")
})
g.TryCatch(func() {
glog.Println("hello")
g.Throw("exception")
glog.Println("world")
}, func(exception interface{}) {
glog.Error(exception)
})
}

View File

@ -1,34 +1,41 @@
# Donators
| Name | Channel | Amount
|---|---|---
|[hailaz](https://gitee.com/hailaz)|gitee|¥20.00
|[ireadx](https://github.com/ireadx)|alipay|¥301.00
|[mg91](https://gitee.com/mg91)|gitee|¥10.00
|[pibigstar](https://github.com/pibigstar)|alipay|¥10.00
|[tiangenglan](https://gitee.com/tiangenglan)|gitee|¥30.00
|[wxkj](https://gitee.com/wxkj)|wechat|¥10.00
|[zhuhuan12](https://gitee.com/zhuhuan12)|gitee|¥50.00
|[zfan_codes](https://gitee.com/zfan_codes)|gitee|¥10.00
|[arden](https://github.com/arden)|alipay|¥10.00
|[macnie](https://www.macnie.com)|wechat|¥100.00
|lah|wechat|¥100.00
|x*z|wechat|¥20.00
|潘兄|wechat|¥100.00
|Fly的狐狸|wechat|¥100.00
|全|alipay|¥100.00
|东东|wechat|¥100.00
|严宇轩|alipay|¥99.99
|土豆相公|alipay|¥66.60
|Hades|alipay|¥66.66
|蔡蔡|wechat|¥666.00
|上海金保证网络科技|bank|¥2000.00
|[foxhack](https://github.com/foxhack)|wechat|¥20.00
|*栈|wechat|¥5.00
|*络|wechat|¥10.00
|R*s|wechat|¥18.88
|粟*e|wechat|¥50.00
| Name | Channel | Amount | Comment
|---|---|--- | ---
|[hailaz](https://gitee.com/hailaz)|gitee|¥20.00 |
|[ireadx](https://github.com/ireadx)|alipay|¥301.00 |
|[mg91](https://gitee.com/mg91)|gitee|¥10.00 |
|[pibigstar](https://github.com/pibigstar)|alipay|¥10.00 |
|[tiangenglan](https://gitee.com/tiangenglan)|gitee|¥30.00 |
|[wxkj](https://gitee.com/wxkj)|wechat|¥10.00 |
|[zhuhuan12](https://gitee.com/zhuhuan12)|gitee|¥50.00 |
|[zfan_codes](https://gitee.com/zfan_codes)|gitee|¥10.00 |
|[arden](https://github.com/arden)|alipay|¥10.00 |
|[macnie](https://www.macnie.com)|wechat|¥100.00 |
|lah|wechat|¥100.00 |
|x*z|wechat|¥20.00 |
|潘兄|wechat|¥100.00 |
|Fly的狐狸|wechat|¥100.00 |
|全|alipay|¥100.00 |
|东东|wechat|¥100.00 |
|严宇轩|alipay|¥99.99 |
|土豆相公|alipay|¥66.60 |
|Hades|alipay|¥66.66 |
|蔡蔡|wechat|¥666.00 | gf越来越强
|上海金保证网络科技|bank|¥2000.00 |
|[foxhack](https://github.com/foxhack)|wechat|¥20.00 |
|*栈|wechat|¥5.00 |
|*络|wechat|¥10.00|
|M*e|wechat|¥20.00|
|*G|wechat|¥10.00|
|E*_|wechat|¥10.00|
|A*y|wechat|¥1.00| 感谢大佬goframe
|K*e|wechat|¥168.00| 感谢老大
|*雨|wechat|¥100.00|
|*洁|wechat|¥10.00|赞助你肥宅快乐水
|R*s|wechat|¥18.88| 谢谢GF辛苦了
|粟*e|wechat|¥50.00|

View File

@ -9,7 +9,12 @@
English | [简体中文](README_ZH.MD)
`GF(GoFrame)` is a modular, full-featured and production-ready application development framework of golang. Providing a series of core components and dozens of practical modules, such as: memcache, configure, validator, logging, array/queue/set/map containers, timer/timing tasks, file/memory lock, object pool, database ORM, etc. Supporting web server integrated with router, cookie, session, middleware, logger, template, https, hooks, rewrites and many more features.
`GF(GoFrame)` is a modular, full-featured and production-ready application development framework of golang.
Providing a series of core components and dozens of practical modules,
such as: memcache, configure, validator, logging, array/queue/set/map containers,
timer/timing tasks, file/memory lock, object pool, database ORM, etc.
Supporting web server integrated with router, cookie, session, middleware, logger,
template, https, hooks, rewrites and many more features.
# Installation
@ -23,7 +28,7 @@ require github.com/gogf/gf latest
# Limitation
```
golang version >= 1.11
golang version >= 1.10
```
# Documentation

View File

@ -8,8 +8,11 @@
[English](README.MD) | 简体中文
`GF(Go Frame)`是一款模块化、高性能、生产级Go应用开发框架。提供了常用的核心开发组件缓存、日志、文件、时间、队列、数组、集合、字符串、定时器、命令行、文件锁、内存锁、对象池、连接池、资源管理、数据校验、数据编码、文件监控、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信、
并发安全容器等等。并提供了Web服务开发的系列核心组件Router、Cookie、Session、Middleware、服务注册、配置管理、模板引擎等等支持热重启、热更新、多域名、多端口、多服务、HTTPS、Rewrite等特性。
`GF(Go Frame)`是一款模块化、高性能、生产级Go基础开发框架。实现了比较完善的基础设施建设,包括常用的核心开发组件,
如:缓存、日志、文件、时间、队列、数组、集合、字符串、定时器、命令行、文件锁、内存锁、对象池、连接池、资源管理、数据校验、数据编码、文件监控、
定时任务、数据库ORM、TCP/UDP组件、进程管理/通信、并发安全容器等等。
并提供了Web服务开发的系列核心组件Router、Cookie、Session、Middleware、服务注册、配置管理、模板引擎等等
支持热重启、热更新、多域名、多端口、多服务、HTTPS、Rewrite等特性。
# 特点
@ -18,7 +21,6 @@
* 简便及可维护性为宗旨;
* 详尽的开发文档及示例;
* 完善的本地中文化支持;
* 致力于项目的通用方案;
* 更适合企业及团队使用;
* 更多请查阅文档及源码;

View File

@ -9,6 +9,7 @@ package garray
import (
"bytes"
"encoding/json"
"fmt"
"github.com/gogf/gf/text/gstr"
"math"
"sort"
@ -45,6 +46,21 @@ func NewArraySize(size int, cap int, safe ...bool) *Array {
}
}
// NewArrayRange creates and returns a array by a range from <start> to <end>
// with step value <step>.
func NewArrayRange(start, end, step int, safe ...bool) *Array {
if step == 0 {
panic(fmt.Sprintf(`invalid step value: %d`, step))
}
slice := make([]interface{}, (end-start+1)/step)
index := 0
for i := start; i <= end; i += step {
slice[index] = i
index++
}
return NewArrayFrom(slice, safe...)
}
// See NewArrayFrom.
func NewFrom(array []interface{}, safe ...bool) *Array {
return NewArrayFrom(array, safe...)

View File

@ -9,6 +9,7 @@ package garray
import (
"bytes"
"encoding/json"
"fmt"
"math"
"sort"
@ -39,6 +40,21 @@ func NewIntArraySize(size int, cap int, safe ...bool) *IntArray {
}
}
// NewIntArrayRange creates and returns a array by a range from <start> to <end>
// with step value <step>.
func NewIntArrayRange(start, end, step int, safe ...bool) *IntArray {
if step == 0 {
panic(fmt.Sprintf(`invalid step value: %d`, step))
}
slice := make([]int, (end-start+1)/step)
index := 0
for i := start; i <= end; i += step {
slice[index] = i
index++
}
return NewIntArrayFrom(slice, safe...)
}
// NewIntArrayFrom creates and returns an array with given slice <array>.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// which is false in default.

View File

@ -9,6 +9,7 @@ package garray
import (
"bytes"
"encoding/json"
"fmt"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gutil"
"math"
@ -50,6 +51,21 @@ func NewSortedArraySize(cap int, comparator func(a, b interface{}) int, safe ...
}
}
// NewSortedArrayRange creates and returns a array by a range from <start> to <end>
// with step value <step>.
func NewSortedArrayRange(start, end, step int, comparator func(a, b interface{}) int, safe ...bool) *SortedArray {
if step == 0 {
panic(fmt.Sprintf(`invalid step value: %d`, step))
}
slice := make([]interface{}, (end-start+1)/step)
index := 0
for i := start; i <= end; i += step {
slice[index] = i
index++
}
return NewSortedArrayFrom(slice, comparator, safe...)
}
// NewSortedArrayFrom creates and returns an sorted array with given slice <array>.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// which is false in default.

View File

@ -9,6 +9,7 @@ package garray
import (
"bytes"
"encoding/json"
"fmt"
"math"
"sort"
@ -53,6 +54,21 @@ func NewSortedIntArraySize(cap int, safe ...bool) *SortedIntArray {
}
}
// NewSortedIntArrayRange creates and returns a array by a range from <start> to <end>
// with step value <step>.
func NewSortedIntArrayRange(start, end, step int, safe ...bool) *SortedIntArray {
if step == 0 {
panic(fmt.Sprintf(`invalid step value: %d`, step))
}
slice := make([]int, (end-start+1)/step)
index := 0
for i := start; i <= end; i += step {
slice[index] = i
index++
}
return NewSortedIntArrayFrom(slice, safe...)
}
// NewIntArrayFrom creates and returns an sorted array with given slice <array>.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// which is false in default.

View File

@ -7,7 +7,9 @@
package glist_test
import (
"container/list"
"fmt"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/container/glist"
)
@ -35,3 +37,64 @@ func Example_basic() {
//0123456789
//0
}
func Example_iterate() {
// concurrent-safe list.
l := glist.NewFrom(garray.NewArrayRange(1, 10, 1).Slice(), true)
// iterate reading from head.
l.RLockFunc(func(list *list.List) {
length := list.Len()
if length > 0 {
for i, e := 0, list.Front(); i < length; i, e = i+1, e.Next() {
fmt.Print(e.Value)
}
}
})
fmt.Println()
// iterate reading from tail.
l.RLockFunc(func(list *list.List) {
length := list.Len()
if length > 0 {
for i, e := 0, list.Back(); i < length; i, e = i+1, e.Prev() {
fmt.Print(e.Value)
}
}
})
fmt.Println()
// iterate reading from head using IteratorAsc.
l.IteratorAsc(func(e *glist.Element) bool {
fmt.Print(e.Value)
return true
})
fmt.Println()
// iterate reading from tail using IteratorDesc.
l.IteratorDesc(func(e *glist.Element) bool {
fmt.Print(e.Value)
return true
})
fmt.Println()
// iterate writing from head.
l.LockFunc(func(list *list.List) {
length := list.Len()
if length > 0 {
for i, e := 0, list.Front(); i < length; i, e = i+1, e.Next() {
if e.Value == 6 {
e.Value = "M"
break
}
}
}
})
fmt.Println(l)
//output:
//12345678910
//10987654321
//12345678910
//10987654321
//[1,2,3,4,5,"M",7,8,9,10]
}

View File

@ -233,15 +233,7 @@ func (v *Var) Map(tags ...string) map[string]interface{} {
// MapStrStr converts <v> to map[string]string.
func (v *Var) MapStrStr(tags ...string) map[string]string {
m := v.Map(tags...)
if len(m) > 0 {
vMap := make(map[string]string)
for k, v := range m {
vMap[k] = gconv.String(v)
}
return vMap
}
return nil
return gconv.MapStrStr(v.Val(), tags...)
}
// MapStrVar converts <v> to map[string]*Var.
@ -264,15 +256,7 @@ func (v *Var) MapDeep(tags ...string) map[string]interface{} {
// MapDeep converts <v> to map[string]string recursively.
func (v *Var) MapStrStrDeep(tags ...string) map[string]string {
m := v.MapDeep(tags...)
if len(m) > 0 {
vMap := make(map[string]string)
for k, v := range m {
vMap[k] = gconv.String(v)
}
return vMap
}
return nil
return gconv.MapStrStrDeep(v.Val(), tags...)
}
// MapStrVarDeep converts <v> to map[string]*Var recursively.

View File

@ -22,6 +22,17 @@ func Encrypt(data interface{}) (encrypt string, err error) {
return EncryptBytes(gconv.Bytes(data))
}
// MustEncrypt encrypts any type of variable using MD5 algorithms.
// It uses gconv package to convert <v> to its bytes type.
// It panics if any error occurs.
func MustEncrypt(data interface{}) string {
result, err := Encrypt(data)
if err != nil {
panic(err)
}
return result
}
// EncryptBytes encrypts <data> using MD5 algorithms.
func EncryptBytes(data []byte) (encrypt string, err error) {
h := md5.New()
@ -31,11 +42,31 @@ func EncryptBytes(data []byte) (encrypt string, err error) {
return fmt.Sprintf("%x", h.Sum(nil)), nil
}
// MustEncryptBytes encrypts <data> using MD5 algorithms.
// It panics if any error occurs.
func MustEncryptBytes(data []byte) string {
result, err := EncryptBytes(data)
if err != nil {
panic(err)
}
return result
}
// EncryptBytes encrypts string <data> using MD5 algorithms.
func EncryptString(data string) (encrypt string, err error) {
return EncryptBytes([]byte(data))
}
// MustEncryptString encrypts string <data> using MD5 algorithms.
// It panics if any error occurs.
func MustEncryptString(data string) string {
result, err := EncryptString(data)
if err != nil {
panic(err)
}
return result
}
// EncryptFile encrypts file content of <path> using MD5 algorithms.
func EncryptFile(path string) (encrypt string, err error) {
f, err := os.Open(path)
@ -50,3 +81,13 @@ func EncryptFile(path string) (encrypt string, err error) {
}
return fmt.Sprintf("%x", h.Sum(nil)), nil
}
// MustEncryptFile encrypts file content of <path> using MD5 algorithms.
// It panics if any error occurs.
func MustEncryptFile(path string) string {
result, err := EncryptFile(path)
if err != nil {
panic(err)
}
return result
}

View File

@ -37,3 +37,13 @@ func EncryptFile(path string) (encrypt string, err error) {
}
return hex.EncodeToString(h.Sum(nil)), nil
}
// MustEncryptFile encrypts file content of <path> using SHA1 algorithms.
// It panics if any error occurs.
func MustEncryptFile(path string) string {
result, err := EncryptFile(path)
if err != nil {
panic(err)
}
return result
}

View File

@ -16,7 +16,6 @@ import (
"github.com/gogf/gf/os/glog"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/container/gring"
"github.com/gogf/gf/container/gtype"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/os/gcache"
@ -84,9 +83,6 @@ type DB interface {
// 设置管理
SetDebug(debug bool)
SetSchema(schema string)
GetQueriedSqls() []*Sql
GetLastSql() *Sql
PrintQueriedSqls()
SetLogger(logger *glog.Logger)
GetLogger() *glog.Logger
SetMaxIdleConnCount(n int)
@ -99,7 +95,9 @@ type DB interface {
getCache() *gcache.Cache
getChars() (charLeft string, charRight string)
getDebug() bool
getPrefix() string
quoteWord(s string) string
quoteString(s 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{}
@ -119,9 +117,9 @@ type dbBase struct {
db DB // 数据库对象
group string // 配置分组名称
debug *gtype.Bool // (默认关闭)是否开启调试模式,当开启时会启用一些调试特性
sqls *gring.Ring // (debug=true时有效)已执行的SQL列表
cache *gcache.Cache // 数据库缓存,包括底层连接池对象缓存及查询缓存;需要注意的是,事务查询不支持查询缓存
schema *gtype.String // 手动切换的数据库名称
prefix string // 表名前缀
tables map[string]map[string]string // 数据库表结构
logger *glog.Logger // 日志管理对象
maxIdleConnCount int // 连接池最大限制的连接数
@ -141,13 +139,14 @@ type Sql struct {
// 表字段结构信息
type TableField struct {
Index int // 用于字段排序(map类型是无序的)
Index int // 用于字段排序(因为map类型是无序的)
Name string // 字段名称
Type string // 字段类型
Null bool // 是否可为null
Key string // 索引信息
Default interface{} // 默认值
Extra string // 其他信息
Comment string // 字段描述
}
// 返回数据表记录值
@ -179,7 +178,7 @@ var (
instances = gmap.NewStrAnyMap(true)
)
// New creates ORM DB object with global configurations.
// New creates and returns an ORM object with global configurations.
// The parameter <name> specifies the configuration group name,
// which is DEFAULT_GROUP_NAME in default.
func New(name ...string) (db DB, err error) {
@ -201,6 +200,7 @@ func New(name ...string) (db DB, err error) {
cache: gcache.New(),
schema: gtype.NewString(),
logger: glog.New(),
prefix: node.Prefix,
maxConnLifetime: gDEFAULT_CONN_MAX_LIFE_TIME,
}
switch node.Type {
@ -222,7 +222,7 @@ func New(name ...string) (db DB, err error) {
return nil, err
}
} else {
return nil, errors.New(fmt.Sprintf("empty database configuration for item name '%s'", group))
return nil, errors.New(fmt.Sprintf(`database configuration node "%s" is not found`, group))
}
}

View File

@ -15,8 +15,6 @@ import (
"regexp"
"strings"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/os/gcache"
"github.com/gogf/gf/os/gtime"
@ -25,58 +23,14 @@ import (
)
const (
gDEFAULT_DEBUG_SQL_LENGTH = 1000
gPATH_FILTER_KEY = "/database/gdb/gdb"
gPATH_FILTER_KEY = "/database/gdb/gdb"
)
var (
wordReg = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`) // Regular expression object for a word.
lastOperatorReg = regexp.MustCompile(`[<>=]+\s*$`) // Regular expression object for a string which has operator at its tail.
// lastOperatorReg is the regular expression object for a string which has operator at its tail.
lastOperatorReg = regexp.MustCompile(`[<>=]+\s*$`)
)
// 获取最近一条执行的sql
func (bs *dbBase) GetLastSql() *Sql {
if bs.sqls == nil {
return nil
}
if v := bs.sqls.Val(); v != nil {
return v.(*Sql)
}
return nil
}
// 获取已经执行的SQL列表(仅在debug=true时有效)
func (bs *dbBase) GetQueriedSqls() []*Sql {
if bs.sqls == nil {
return nil
}
array := make([]*Sql, 0)
bs.sqls.Prev()
bs.sqls.RLockIteratorPrev(func(value interface{}) bool {
if value == nil {
return false
}
array = append(array, value.(*Sql))
return true
})
return array
}
// 打印已经执行的SQL列表(仅在debug=true时有效)
func (bs *dbBase) PrintQueriedSqls() {
sqlSlice := bs.GetQueriedSqls()
for k, v := range sqlSlice {
fmt.Println(len(sqlSlice)-k, ":")
fmt.Println(" Sql :", v.Sql)
fmt.Println(" Args :", v.Args)
fmt.Println(" Format :", v.Format)
fmt.Println(" Error :", v.Error)
fmt.Println(" Start :", gtime.NewFromTimeStamp(v.Start).Format("Y-m-d H:i:s.u"))
fmt.Println(" End :", gtime.NewFromTimeStamp(v.End).Format("Y-m-d H:i:s.u"))
fmt.Println(" Cost :", v.End-v.Start, "ms")
}
}
// 打印SQL对象(仅在debug=true时有效)
func (bs *dbBase) printSql(v *Sql) {
s := fmt.Sprintf("[%d ms] %s", v.End-v.Start, v.Format)
@ -113,7 +67,6 @@ func (bs *dbBase) doQuery(link dbLink, query string, args ...interface{}) (rows
Start: mTime1,
End: mTime2,
}
bs.sqls.Put(s)
bs.printSql(s)
} else {
rows, err = link.Query(query, args...)
@ -140,9 +93,9 @@ func (bs *dbBase) doExec(link dbLink, query string, args ...interface{}) (result
query, args = formatQuery(query, args)
query = bs.db.handleSqlBeforeExec(query)
if bs.db.getDebug() {
mTime1 := gtime.Millisecond()
mTime1 := gtime.TimestampMilli()
result, err = link.Exec(query, args...)
mTime2 := gtime.Millisecond()
mTime2 := gtime.TimestampMilli()
s := &Sql{
Sql: query,
Args: args,
@ -151,7 +104,6 @@ func (bs *dbBase) doExec(link dbLink, query string, args ...interface{}) (result
Start: mTime1,
End: mTime2,
}
bs.sqls.Put(s)
bs.printSql(s)
} else {
result, err = link.Exec(query, args...)
@ -600,6 +552,11 @@ func (bs *dbBase) getCache() *gcache.Cache {
return bs.cache
}
// 获得表名前缀
func (bs *dbBase) getPrefix() string {
return bs.prefix
}
// 将数据查询的列表数据*sql.Rows转换为Result类型
func (bs *dbBase) rowsToResult(rows *sql.Rows) (Result, error) {
if !rows.Next() {
@ -648,14 +605,18 @@ func (bs *dbBase) rowsToResult(rows *sql.Rows) (Result, error) {
return records, nil
}
// 使用关键字操作符转义给定字符串。
// 如果给定的字符串不为单词,那么不转义,直接返回该字符串。
// 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 {
charLeft, charRight := bs.db.getChars()
if wordReg.MatchString(s) && !gstr.ContainsAny(s, charLeft+charRight) {
return charLeft + s + charRight
}
return s
return doQuoteWord(s, charLeft, charRight)
}
// quoteString quotes string with quote chars. Strings like:
// "user", "user u", "user,user_detail", "user u, user_detail ut", "u.id asc".
func (bs *dbBase) quoteString(s string) string {
charLeft, charRight := bs.db.getChars()
return doQuoteString(s, charLeft, charRight)
}
// 动态切换数据库

View File

@ -12,8 +12,6 @@ import (
"time"
"github.com/gogf/gf/os/glog"
"github.com/gogf/gf/container/gring"
)
const (
@ -36,6 +34,7 @@ type ConfigNode struct {
Type string // 数据库类型mysql, sqlite, mssql, pgsql, oracle
Role string // (可选默认为master)数据库的角色用于主从操作分离至少需要有一个master参数值master, slave
Debug bool // (可选)开启调试模式
Prefix string // (可选)表名前缀
Weight int // (可选)用于负载均衡的权重计算,当集群中只有一个节点时,权重没有任何意义
Charset string // (可选,默认为 utf8)编码,默认为 utf8
LinkInfo string // (可选)自定义链接信息,当该字段被设置值时,以上链接字段(Host,Port,User,Pass,Name)将失效(该字段是一个扩展功能)
@ -159,9 +158,6 @@ func (bs *dbBase) SetDebug(debug bool) {
return
}
bs.debug.Set(debug)
if debug && bs.sqls == nil {
bs.sqls = gring.New(gDEFAULT_DEBUG_SQL_LENGTH, true)
}
}
// 获取是否开启调试服务

View File

@ -14,6 +14,7 @@ import (
"github.com/gogf/gf/internal/empty"
"github.com/gogf/gf/os/gtime"
"reflect"
"regexp"
"strings"
"time"
@ -24,17 +25,17 @@ import (
"github.com/gogf/gf/util/gconv"
)
// Type assert api for String.
// apiString is the type assert api for String.
type apiString interface {
String() string
}
// Type assert api for Iterator.
// apiIterator is the type assert api for Iterator.
type apiIterator interface {
Iterator(f func(key, value interface{}) bool)
}
// Type assert api for Interfaces.
// apiInterfacesis the type assert api for Interfaces.
type apiInterfaces interface {
Interfaces() []interface{}
}
@ -45,23 +46,118 @@ const (
ORM_TAG_FOR_PRIMARY = "primary"
)
// 获得struct对象对应的where查询条件
var (
// quoteWordReg is the regular expression object for a word check.
quoteWordReg = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`)
)
// 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.
func doQuoteWord(s, charLeft, charRight string) string {
if quoteWordReg.MatchString(s) && !gstr.ContainsAny(s, charLeft+charRight) {
return charLeft + s + charRight
}
return s
}
// doQuoteString quotes string with quote chars. It handles strings like:
// "user", "user u", "user,user_detail", "user u, user_detail ut", "u.id asc".
func doQuoteString(s, charLeft, charRight string) string {
array1 := gstr.SplitAndTrim(s, ",")
for k1, v1 := range array1 {
array2 := gstr.SplitAndTrim(v1, " ")
array3 := gstr.SplitAndTrim(array2[0], ".")
if len(array3) == 1 {
array3[0] = doQuoteWord(array3[0], charLeft, charRight)
} else if len(array3) == 2 {
array3[1] = doQuoteWord(array3[1], charLeft, charRight)
}
array2[0] = gstr.Join(array3, ".")
array1[k1] = gstr.Join(array2, " ")
}
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{}) {
array := ([]string)(nil)
for tag, field := range structs.TagMapField(pointer, []string{ORM_TAG_FOR_STRUCT}, true) {
array = strings.Split(tag, ",")
for _, field := range structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}, true) {
array = strings.Split(field.Tag, ",")
if len(array) > 1 && gstr.InArray([]string{ORM_TAG_FOR_UNIQUE, ORM_TAG_FOR_PRIMARY}, array[1]) {
return array[0], []interface{}{field.Value()}
}
if len(where) > 0 {
where += " "
}
where += tag + "=?"
where += field.Tag + "=?"
args = append(args, field.Value())
}
return
}
// GetPrimaryKey retrieves and returns primary key field name from given struct.
func GetPrimaryKey(pointer interface{}) string {
array := ([]string)(nil)
for _, field := range structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}, true) {
array = strings.Split(field.Tag, ",")
if len(array) > 1 && array[1] == ORM_TAG_FOR_PRIMARY {
return array[0]
}
}
return ""
}
// GetPrimaryKeyCondition returns a new where condition by primary field name.
// The optional parameter <where> is like follows:
// 123, []int{1, 2, 3}, "john", []string{"john", "smith"}
// g.Map{"id": g.Slice{1,2,3}}, g.Map{"id": 1, "name": "john"}, etc.
//
// Note that it returns the given <where> parameter directly if there's the <primary> is empty.
func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondition []interface{}) {
if len(where) == 0 {
return nil
}
if primary == "" {
return where
}
if len(where) == 1 {
rv := reflect.ValueOf(where[0])
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Map, reflect.Struct:
break
default:
return []interface{}{map[string]interface{}{
primary: where[0],
}}
}
}
return where
}
// 获得orm标签与属性的映射关系
func GetOrmMappingOfStruct(pointer interface{}) map[string]string {
mapping := make(map[string]string)
@ -113,7 +209,6 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (
})
break
}
// TODO garray support.
for key, value := range varToMapDeep(where) {
if omitEmpty && empty.IsEmpty(value) {
continue
@ -135,7 +230,7 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (
if gstr.Pos(newWhere, "?") == -1 {
if lastOperatorReg.MatchString(newWhere) {
newWhere += "?"
} else if wordReg.MatchString(newWhere) {
} else if quoteWordReg.MatchString(newWhere) {
newWhere += "=?"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,8 @@ import (
"github.com/gogf/gf/util/gconv"
)
// convertValue converts field value from database type to golang variable type.
// convertValue automatically checks and converts field value from database type
// to golang variable type.
func (bs *dbBase) convertValue(fieldValue []byte, fieldType string) interface{} {
t, _ := gregex.ReplaceString(`\(.+\)`, "", fieldType)
t = strings.ToLower(t)
@ -97,9 +98,9 @@ func (bs *dbBase) convertValue(fieldValue []byte, fieldType string) interface{}
}
}
// 将map的数据按照fields进行过滤只保留与表字段同名的数据
// filterFields removes all key-value pairs which are not the field of given table.
func (bs *dbBase) filterFields(table string, data map[string]interface{}) map[string]interface{} {
// It must use data copy here to avoid changing the origin data map.
// It must use data copy here to avoid its changing the origin data map.
newDataMap := make(map[string]interface{}, len(data))
if fields, err := bs.db.TableFields(table); err == nil {
for k, v := range data {
@ -111,7 +112,7 @@ func (bs *dbBase) filterFields(table string, data map[string]interface{}) map[st
return newDataMap
}
// 返回当前数据库所有的数据表名称
// Tables returns the table name array of current schema.
func (bs *dbBase) Tables() (tables []string, err error) {
result := (Result)(nil)
result, err = bs.GetAll(`SHOW TABLES`)
@ -126,12 +127,15 @@ func (bs *dbBase) Tables() (tables []string, err error) {
return
}
// 获得指定表表的数据结构构造成map哈希表返回其中键名为表字段名称键值为字段数据结构.
// TableFields retrieves and returns the fields of given table.
// Note that it returns a map containing the field name and its corresponding fields.
// As a map is unsorted, the TableField struct has a "Index" field marks its sequence in the fields.
//
// It's using cache feature to enhance the performance, which is never expired util the process restarts.
func (bs *dbBase) TableFields(table string) (fields map[string]*TableField, err error) {
// 缓存不存在时会查询数据表结构,缓存后不过期,直至程序重启(重新部署)
v := bs.cache.GetOrSetFunc("table_fields_"+table, func() interface{} {
result := (Result)(nil)
result, err = bs.GetAll(fmt.Sprintf(`SHOW COLUMNS FROM %s`, bs.db.quoteWord(table)))
result, err = bs.GetAll(fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, bs.db.quoteWord(table)))
if err != nil {
return nil
}
@ -145,6 +149,7 @@ func (bs *dbBase) TableFields(table string) (fields map[string]*TableField, err
Key: m["Key"].String(),
Default: m["Default"].Val(),
Extra: m["Extra"].String(),
Comment: m["Comment"].String(),
}
}
return fields

View File

@ -9,23 +9,24 @@ package gdb
import (
"database/sql"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/encoding/gparser"
)
// 将记录结果转换为JSON字符串
// Json converts <r> to JSON format content.
func (r Record) Json() string {
content, _ := gparser.VarToJson(r.Map())
return string(content)
return gconv.UnsafeBytesToStr(content)
}
// 将记录结果转换为XML字符串
// Xml converts <r> to XML format content.
func (r Record) Xml(rootTag ...string) string {
content, _ := gparser.VarToXml(r.Map(), rootTag...)
return string(content)
return gconv.UnsafeBytesToStr(content)
}
// 将Record转换为Map类型
// Map converts <r> to map[string]interface{}.
func (r Record) Map() Map {
m := make(map[string]interface{})
for k, v := range r {
@ -34,12 +35,13 @@ func (r Record) Map() Map {
return m
}
// 将Record转换为常用的gmap.StrAnyMap类型
// GMap converts <r> to a gmap.
func (r Record) GMap() *gmap.StrAnyMap {
return gmap.NewStrAnyMapFrom(r.Map())
}
// 将Map变量映射到指定的struct对象中注意参数应当是一个对象的指针
// Struct converts <r> to a struct.
// Note that the parameter <pointer> should be type of *struct/**struct.
func (r Record) Struct(pointer interface{}) error {
if r == nil {
return sql.ErrNoRows

View File

@ -14,19 +14,19 @@ import (
"github.com/gogf/gf/encoding/gparser"
)
// 将结果集转换为JSON字符串
// Json converts <r> to JSON format content.
func (r Result) Json() string {
content, _ := gparser.VarToJson(r.List())
return string(content)
}
// 将结果集转换为XML字符串
// Xml converts <r> to XML format content.
func (r Result) Xml(rootTag ...string) string {
content, _ := gparser.VarToXml(r.List(), rootTag...)
return string(content)
}
// 将结果集转换为List类型返回便于json处理
// List converts <r> to a List.
func (r Result) List() List {
l := make(List, len(r))
for k, v := range r {
@ -35,7 +35,7 @@ func (r Result) List() List {
return l
}
// 将结果列表按照指定的字段值做map[string]Map
// MapKeyStr converts <r> to a map[string]Map of which key is specified by <key>.
func (r Result) MapKeyStr(key string) map[string]Map {
m := make(map[string]Map)
for _, item := range r {
@ -46,7 +46,7 @@ func (r Result) MapKeyStr(key string) map[string]Map {
return m
}
// 将结果列表按照指定的字段值做map[int]Map
// MapKeyInt converts <r> to a map[int]Map of which key is specified by <key>.
func (r Result) MapKeyInt(key string) map[int]Map {
m := make(map[int]Map)
for _, item := range r {
@ -57,7 +57,7 @@ func (r Result) MapKeyInt(key string) map[int]Map {
return m
}
// 将结果列表按照指定的字段值做map[uint]Map
// MapKeyUint converts <r> to a map[uint]Map of which key is specified by <key>.
func (r Result) MapKeyUint(key string) map[uint]Map {
m := make(map[uint]Map)
for _, item := range r {
@ -68,7 +68,7 @@ func (r Result) MapKeyUint(key string) map[uint]Map {
return m
}
// 将结果列表按照指定的字段值做map[string]Record
// RecordKeyInt converts <r> to a map[int]Record of which key is specified by <key>.
func (r Result) RecordKeyStr(key string) map[string]Record {
m := make(map[string]Record)
for _, item := range r {
@ -79,7 +79,7 @@ func (r Result) RecordKeyStr(key string) map[string]Record {
return m
}
// 将结果列表按照指定的字段值做map[int]Record
// RecordKeyInt converts <r> to a map[int]Record of which key is specified by <key>.
func (r Result) RecordKeyInt(key string) map[int]Record {
m := make(map[int]Record)
for _, item := range r {
@ -90,7 +90,7 @@ func (r Result) RecordKeyInt(key string) map[int]Record {
return m
}
// 将结果列表按照指定的字段值做map[uint]Record
// RecordKeyUint converts <r> to a map[uint]Record of which key is specified by <key>.
func (r Result) RecordKeyUint(key string) map[uint]Record {
m := make(map[uint]Record)
for _, item := range r {
@ -101,7 +101,8 @@ func (r Result) RecordKeyUint(key string) map[uint]Record {
return m
}
// 将结果列表转换为指定对象的slice
// Structs converts <r> to struct slice.
// Note that the parameter <pointer> should be type of *[]struct/*[]*struct.
func (r Result) Structs(pointer interface{}) (err error) {
l := len(r)
if l == 0 {

View File

@ -21,14 +21,16 @@ const (
TABLE = "user"
SCHEMA1 = "test1"
SCHEMA2 = "test2"
PREFIX1 = "gf_"
)
var (
db gdb.DB
db gdb.DB
dbPrefix gdb.DB
)
func InitMysql() {
node := gdb.ConfigNode{
nodeDefault := gdb.ConfigNode{
Host: "127.0.0.1",
Port: "3306",
User: "root",
@ -42,35 +44,62 @@ func InitMysql() {
MaxOpenConnCount: 10,
MaxConnLifetime: 600,
}
gdb.AddConfigNode("test", node)
gdb.AddConfigNode(gdb.DEFAULT_GROUP_NAME, node)
nodePrefix := nodeDefault
nodePrefix.Prefix = PREFIX1
gdb.AddConfigNode("test", nodeDefault)
gdb.AddConfigNode("prefix", nodePrefix)
gdb.AddConfigNode(gdb.DEFAULT_GROUP_NAME, nodeDefault)
// Default db.
if r, err := gdb.New(); err != nil {
gtest.Error(err)
} else {
db = r
}
schemaTemplate := "CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET UTF8"
if _, err := db.Exec(fmt.Sprintf(schemaTemplate, SCHEMA1)); err != nil {
gtest.Error(err)
}
if _, err := db.Exec(fmt.Sprintf(schemaTemplate, SCHEMA2)); err != nil {
gtest.Error(err)
}
db.SetSchema(SCHEMA1)
createTable(TABLE)
// Prefix db.
if r, err := gdb.New("prefix"); err != nil {
gtest.Error(err)
} else {
dbPrefix = r
}
if _, err := dbPrefix.Exec(fmt.Sprintf(schemaTemplate, SCHEMA1)); err != nil {
gtest.Error(err)
}
if _, err := dbPrefix.Exec(fmt.Sprintf(schemaTemplate, SCHEMA2)); err != nil {
gtest.Error(err)
}
dbPrefix.SetSchema(SCHEMA1)
createTable(TABLE)
}
func createTable(table ...string) (name string) {
func createTable(table ...string) string {
return createTableWithDb(db, table...)
}
func createInitTable(table ...string) string {
return createInitTableWithDb(db, table...)
}
func dropTable(table string) {
dropTableWithDb(db, table)
}
func createTableWithDb(db gdb.DB, table ...string) (name string) {
if len(table) > 0 {
name = table[0]
} else {
name = fmt.Sprintf(`%s_%d`, TABLE, gtime.Nanosecond())
}
dropTable(name)
dropTableWithDb(db, name)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
@ -86,8 +115,8 @@ func createTable(table ...string) (name string) {
return
}
func createInitTable(table ...string) (name string) {
name = createTable(table...)
func createInitTableWithDb(db gdb.DB, table ...string) (name string) {
name = createTableWithDb(db, table...)
array := garray.New(true)
for i := 1; i <= INIT_DATA_SIZE; i++ {
array.Append(g.Map{
@ -98,7 +127,8 @@ func createInitTable(table ...string) (name string) {
"create_time": gtime.NewFromStr("2018-10-24 10:00:00").String(),
})
}
result, err := db.Table(name).Data(array.Slice()).Insert()
result, err := db.BatchInsert(name, array.Slice())
gtest.Assert(err, nil)
n, e := result.RowsAffected()
@ -107,7 +137,7 @@ func createInitTable(table ...string) (name string) {
return
}
func dropTable(table string) {
func dropTableWithDb(db gdb.DB, table string) {
if _, err := db.Exec(fmt.Sprintf("DROP TABLE IF EXISTS `%s`", table)); err != nil {
gtest.Error(err)
}

View File

@ -0,0 +1,77 @@
// 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 gdb
import (
"github.com/gogf/gf/test/gtest"
"testing"
)
func Test_Func_doQuoteWord(t *testing.T) {
gtest.Case(t, func() {
array := map[string]string{
"user": "`user`",
"user u": "user u",
"user_detail": "`user_detail`",
"user,user_detail": "user,user_detail",
"user u, user_detail ut": "user u, user_detail ut",
"u.id asc": "u.id asc",
"u.id asc, ut.uid desc": "u.id asc, ut.uid desc",
}
for k, v := range array {
gtest.Assert(doQuoteWord(k, "`", "`"), v)
}
})
}
func Test_Func_doQuoteString(t *testing.T) {
gtest.Case(t, func() {
// "user", "user u", "user,user_detail", "user u, user_detail ut", "u.id asc".
array := map[string]string{
"user": "`user`",
"user u": "`user` u",
"user,user_detail": "`user`,`user_detail`",
"user u, user_detail ut": "`user` u,`user_detail` ut",
"u.id asc": "u.`id` asc",
"u.id asc, ut.uid desc": "u.`id` asc,ut.`uid` desc",
}
for k, v := range array {
gtest.Assert(doQuoteString(k, "`", "`"), v)
}
})
}
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",
}
for k, v := range array {
gtest.Assert(addTablePrefix(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",
}
for k, v := range array {
gtest.Assert(addTablePrefix(k, prefix), v)
}
})
}

View File

@ -200,7 +200,7 @@ func Test_Model_Update(t *testing.T) {
defer dropTable(table)
// UPDATE...LIMIT
gtest.Case(t, func() {
result, err := db.Table(table).Data("nickname", "T100").OrderBy("id desc").Limit(2).Update()
result, err := db.Table(table).Data("nickname", "T100").Order("id desc").Limit(2).Update()
gtest.Assert(err, nil)
n, _ := result.RowsAffected()
gtest.Assert(n, 2)
@ -246,10 +246,10 @@ func Test_Model_Clone(t *testing.T) {
count, err := md.Count()
gtest.Assert(err, nil)
record, err := md.OrderBy("id DESC").One()
record, err := md.Order("id DESC").One()
gtest.Assert(err, nil)
result, err := md.OrderBy("id ASC").All()
result, err := md.Order("id ASC").All()
gtest.Assert(err, nil)
gtest.Assert(count, 2)
@ -327,7 +327,7 @@ func Test_Model_Safe(t *testing.T) {
gtest.Assert(err, nil)
gtest.Assert(count, 2)
all, err := md2.OrderBy("id asc").All()
all, err := md2.Order("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(all), 2)
gtest.Assert(all[0]["id"].Int(), 1)
@ -342,7 +342,7 @@ func Test_Model_Safe(t *testing.T) {
gtest.Assert(err, nil)
gtest.Assert(count, 3)
all, err = md3.OrderBy("id asc").All()
all, err = md3.Order("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(all), 3)
gtest.Assert(all[0]["id"].Int(), 4)
@ -371,6 +371,44 @@ func Test_Model_All(t *testing.T) {
})
}
func Test_Model_FindAll(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.Case(t, func() {
result, err := db.Table(table).FindAll(5)
gtest.Assert(err, nil)
gtest.Assert(len(result), 1)
gtest.Assert(result[0]["id"].Int(), 5)
})
gtest.Case(t, func() {
result, err := db.Table(table).Order("id asc").FindAll("id", 8)
gtest.Assert(err, nil)
gtest.Assert(len(result), 1)
gtest.Assert(result[0]["id"].Int(), 8)
})
gtest.Case(t, func() {
result, err := db.Table(table).Order("id asc").FindAll(g.Slice{3, 9})
gtest.Assert(err, nil)
gtest.Assert(len(result), 2)
gtest.Assert(result[0]["id"].Int(), 3)
gtest.Assert(result[1]["id"].Int(), 9)
})
gtest.Case(t, func() {
result, err := db.Table(table).FindAll()
gtest.Assert(err, nil)
gtest.Assert(len(result), INIT_DATA_SIZE)
})
gtest.Case(t, func() {
result, err := db.Table(table).Where("id<0").FindAll()
gtest.Assert(result, nil)
gtest.Assert(err, nil)
})
}
func Test_Model_One(t *testing.T) {
table := createInitTable()
defer dropTable(table)
@ -387,9 +425,45 @@ func Test_Model_One(t *testing.T) {
})
}
func Test_Model_FindOne(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.Case(t, func() {
record, err := db.Table(table).FindOne(1)
gtest.Assert(err, nil)
gtest.Assert(record["nickname"].String(), "name_1")
})
gtest.Case(t, func() {
record, err := db.Table(table).FindOne(3)
gtest.Assert(err, nil)
gtest.Assert(record["nickname"].String(), "name_3")
})
gtest.Case(t, func() {
record, err := db.Table(table).Where("id", 1).FindOne()
gtest.Assert(err, nil)
gtest.Assert(record["nickname"].String(), "name_1")
})
gtest.Case(t, func() {
record, err := db.Table(table).FindOne("id", 9)
gtest.Assert(err, nil)
gtest.Assert(record["nickname"].String(), "name_9")
})
gtest.Case(t, func() {
record, err := db.Table(table).Where("id", 0).FindOne()
gtest.Assert(err, nil)
gtest.Assert(record, nil)
})
}
func Test_Model_Value(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.Case(t, func() {
value, err := db.Table(table).Fields("nickname").Where("id", 1).Value()
gtest.Assert(err, nil)
@ -403,6 +477,35 @@ func Test_Model_Value(t *testing.T) {
})
}
func Test_Model_FindValue(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.Case(t, func() {
value, err := db.Table(table).FindValue("nickname", 1)
gtest.Assert(err, nil)
gtest.Assert(value.String(), "name_1")
})
gtest.Case(t, func() {
value, err := db.Table(table).Order("id desc").FindValue("nickname")
gtest.Assert(err, nil)
gtest.Assert(value.String(), "name_10")
})
gtest.Case(t, func() {
value, err := db.Table(table).Fields("nickname").Where("id", 1).FindValue()
gtest.Assert(err, nil)
gtest.Assert(value.String(), "name_1")
})
gtest.Case(t, func() {
value, err := db.Table(table).Fields("nickname").Where("id", 0).FindValue()
gtest.Assert(err, nil)
gtest.Assert(value, nil)
})
}
func Test_Model_Count(t *testing.T) {
table := createInitTable()
defer dropTable(table)
@ -413,6 +516,26 @@ func Test_Model_Count(t *testing.T) {
})
}
func Test_Model_FindCount(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.Case(t, func() {
count, err := db.Table(table).FindCount(g.Slice{1, 3})
gtest.Assert(err, nil)
gtest.Assert(count, 2)
})
gtest.Case(t, func() {
count, err := db.Table(table).FindCount(g.Slice{1, 300000})
gtest.Assert(err, nil)
gtest.Assert(count, 1)
})
gtest.Case(t, func() {
count, err := db.Table(table).FindCount()
gtest.Assert(err, nil)
gtest.Assert(count, INIT_DATA_SIZE)
})
}
func Test_Model_Select(t *testing.T) {
table := createInitTable()
defer dropTable(table)
@ -514,7 +637,7 @@ func Test_Model_Structs(t *testing.T) {
CreateTime gtime.Time
}
var users []User
err := db.Table(table).OrderBy("id asc").Structs(&users)
err := db.Table(table).Order("id asc").Structs(&users)
if err != nil {
gtest.Error(err)
}
@ -537,7 +660,7 @@ func Test_Model_Structs(t *testing.T) {
CreateTime *gtime.Time
}
var users []*User
err := db.Table(table).OrderBy("id asc").Structs(&users)
err := db.Table(table).Order("id asc").Structs(&users)
if err != nil {
gtest.Error(err)
}
@ -560,7 +683,7 @@ func Test_Model_Structs(t *testing.T) {
CreateTime *gtime.Time
}
var users []*User
err := db.Table(table).OrderBy("id asc").Scan(&users)
err := db.Table(table).Order("id asc").Scan(&users)
if err != nil {
gtest.Error(err)
}
@ -629,7 +752,7 @@ func Test_Model_Scan(t *testing.T) {
CreateTime gtime.Time
}
var users []User
err := db.Table(table).OrderBy("id asc").Scan(&users)
err := db.Table(table).Order("id asc").Scan(&users)
gtest.Assert(err, nil)
gtest.Assert(len(users), INIT_DATA_SIZE)
gtest.Assert(users[0].Id, 1)
@ -649,7 +772,7 @@ func Test_Model_Scan(t *testing.T) {
CreateTime *gtime.Time
}
var users []*User
err := db.Table(table).OrderBy("id asc").Scan(&users)
err := db.Table(table).Order("id asc").Scan(&users)
gtest.Assert(err, nil)
gtest.Assert(len(users), INIT_DATA_SIZE)
gtest.Assert(users[0].Id, 1)
@ -683,7 +806,7 @@ func Test_Model_OrderBy(t *testing.T) {
defer dropTable(table)
gtest.Case(t, func() {
result, err := db.Table(table).OrderBy("id DESC").Select()
result, err := db.Table(table).Order("id DESC").Select()
gtest.Assert(err, nil)
gtest.Assert(len(result), INIT_DATA_SIZE)
gtest.Assert(result[0]["nickname"].String(), fmt.Sprintf("name_%d", INIT_DATA_SIZE))
@ -724,7 +847,7 @@ func Test_Model_Where(t *testing.T) {
gtest.Case(t, func() {
result, err := db.Table(table).Where(g.Map{
"passport like": "user_1%",
}).OrderBy("id asc").All()
}).Order("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 2)
gtest.Assert(result[0].GMap().Get("id"), 1)
@ -870,7 +993,7 @@ func Test_Model_Where(t *testing.T) {
"create_time > 0": nil,
"id": g.Slice{1, 2, 3},
}
result, err := db.Table(table).Where(conditions).OrderBy("id asc").All()
result, err := db.Table(table).Where(conditions).Order("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 3)
gtest.Assert(result[0]["id"].Int(), 1)
@ -885,7 +1008,7 @@ func Test_Model_Where(t *testing.T) {
"create_time > ?": 0,
"id in(?)": g.Slice{1, 2, 3},
}
result, err := db.Table(table).Where(conditions).OrderBy("id asc").All()
result, err := db.Table(table).Where(conditions).Order("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 3)
gtest.Assert(result[0]["id"].Int(), 1)
@ -906,7 +1029,7 @@ func Test_Model_Where(t *testing.T) {
})
// slice single
gtest.Case(t, func() {
result, err := db.Table(table).Where("id IN(?)", g.Slice{1, 3}).OrderBy("id ASC").All()
result, err := db.Table(table).Where("id IN(?)", g.Slice{1, 3}).Order("id ASC").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 2)
gtest.Assert(result[0]["id"].Int(), 1)
@ -914,7 +1037,7 @@ func Test_Model_Where(t *testing.T) {
})
// slice + string
gtest.Case(t, func() {
result, err := db.Table(table).Where("nickname=? AND id IN(?)", "name_3", g.Slice{1, 3}).OrderBy("id ASC").All()
result, err := db.Table(table).Where("nickname=? AND id IN(?)", "name_3", g.Slice{1, 3}).Order("id ASC").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 1)
gtest.Assert(result[0]["id"].Int(), 3)
@ -924,7 +1047,7 @@ func Test_Model_Where(t *testing.T) {
result, err := db.Table(table).Where(g.Map{
"id": g.Slice{1, 3},
"nickname": "name_3",
}).OrderBy("id ASC").All()
}).Order("id ASC").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 1)
gtest.Assert(result[0]["id"].Int(), 3)
@ -938,7 +1061,265 @@ func Test_Model_Where(t *testing.T) {
result, err := db.Table(table).Where(User{
Ids: []int{1, 3},
Nickname: "name_3",
}).OrderBy("id ASC").All()
}).Order("id ASC").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 1)
gtest.Assert(result[0]["id"].Int(), 3)
})
}
func Test_Model_WherePri(t *testing.T) {
table := createInitTable()
defer dropTable(table)
// primary key
gtest.Case(t, func() {
one, err := db.Table(table).WherePri(3).One()
gtest.Assert(err, nil)
gtest.AssertNE(one, nil)
gtest.Assert(one["id"].Int(), 3)
})
gtest.Case(t, func() {
all, err := db.Table(table).WherePri(g.Slice{3, 9}).Order("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(all), 2)
gtest.Assert(all[0]["id"].Int(), 3)
gtest.Assert(all[1]["id"].Int(), 9)
})
// string
gtest.Case(t, func() {
result, err := db.Table(table).WherePri("id=? and nickname=?", 3, "name_3").One()
gtest.Assert(err, nil)
gtest.AssertGT(len(result), 0)
gtest.Assert(result["id"].Int(), 3)
})
// slice parameter
gtest.Case(t, func() {
result, err := db.Table(table).WherePri("id=? and nickname=?", g.Slice{3, "name_3"}).One()
gtest.Assert(err, nil)
gtest.AssertGT(len(result), 0)
gtest.Assert(result["id"].Int(), 3)
})
// map like
gtest.Case(t, func() {
result, err := db.Table(table).WherePri(g.Map{
"passport like": "user_1%",
}).Order("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 2)
gtest.Assert(result[0].GMap().Get("id"), 1)
gtest.Assert(result[1].GMap().Get("id"), 10)
})
// map + slice parameter
gtest.Case(t, func() {
result, err := db.Table(table).WherePri(g.Map{
"id": g.Slice{1, 2, 3},
"passport": g.Slice{"user_2", "user_3"},
}).And("id=? and nickname=?", g.Slice{3, "name_3"}).One()
gtest.Assert(err, nil)
gtest.AssertGT(len(result), 0)
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table(table).WherePri(g.Map{
"id": g.Slice{1, 2, 3},
"passport": g.Slice{"user_2", "user_3"},
}).Or("nickname=?", g.Slice{"name_4"}).And("id", 3).One()
gtest.Assert(err, nil)
gtest.AssertGT(len(result), 0)
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table(table).WherePri("id=3", g.Slice{}).One()
gtest.Assert(err, nil)
gtest.AssertGT(len(result), 0)
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table(table).WherePri("id=?", g.Slice{3}).One()
gtest.Assert(err, nil)
gtest.AssertGT(len(result), 0)
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table(table).WherePri("id", 3).One()
gtest.Assert(err, nil)
gtest.AssertGT(len(result), 0)
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table(table).WherePri("id", 3).WherePri("nickname", "name_3").One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table(table).WherePri("id", 3).And("nickname", "name_3").One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table(table).WherePri("id", 30).Or("nickname", "name_3").One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table(table).WherePri("id", 30).Or("nickname", "name_3").And("id>?", 1).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table(table).WherePri("id", 30).Or("nickname", "name_3").And("id>", 1).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
// slice
gtest.Case(t, func() {
result, err := db.Table(table).WherePri("id=? AND nickname=?", g.Slice{3, "name_3"}...).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table(table).WherePri("id=? AND nickname=?", g.Slice{3, "name_3"}).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table(table).WherePri("passport like ? and nickname like ?", g.Slice{"user_3", "name_3"}).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
// map
gtest.Case(t, func() {
result, err := db.Table(table).WherePri(g.Map{"id": 3, "nickname": "name_3"}).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
// map key operator
gtest.Case(t, func() {
result, err := db.Table(table).WherePri(g.Map{"id>": 1, "id<": 3}).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 2)
})
// gmap.Map
gtest.Case(t, func() {
result, err := db.Table(table).WherePri(gmap.NewFrom(g.MapAnyAny{"id": 3, "nickname": "name_3"})).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
// gmap.Map key operator
gtest.Case(t, func() {
result, err := db.Table(table).WherePri(gmap.NewFrom(g.MapAnyAny{"id>": 1, "id<": 3})).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 2)
})
// list map
gtest.Case(t, func() {
result, err := db.Table(table).WherePri(gmap.NewListMapFrom(g.MapAnyAny{"id": 3, "nickname": "name_3"})).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
// list map key operator
gtest.Case(t, func() {
result, err := db.Table(table).WherePri(gmap.NewListMapFrom(g.MapAnyAny{"id>": 1, "id<": 3})).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 2)
})
// tree map
gtest.Case(t, func() {
result, err := db.Table(table).WherePri(gmap.NewTreeMapFrom(gutil.ComparatorString, g.MapAnyAny{"id": 3, "nickname": "name_3"})).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
// tree map key operator
gtest.Case(t, func() {
result, err := db.Table(table).WherePri(gmap.NewTreeMapFrom(gutil.ComparatorString, g.MapAnyAny{"id>": 1, "id<": 3})).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 2)
})
// complicated where 1
gtest.Case(t, func() {
//db.SetDebug(true)
conditions := g.Map{
"nickname like ?": "%name%",
"id between ? and ?": g.Slice{1, 3},
"id > 0": nil,
"create_time > 0": nil,
"id": g.Slice{1, 2, 3},
}
result, err := db.Table(table).WherePri(conditions).Order("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 3)
gtest.Assert(result[0]["id"].Int(), 1)
})
// complicated where 2
gtest.Case(t, func() {
//db.SetDebug(true)
conditions := g.Map{
"nickname like ?": "%name%",
"id between ? and ?": g.Slice{1, 3},
"id >= ?": 1,
"create_time > ?": 0,
"id in(?)": g.Slice{1, 2, 3},
}
result, err := db.Table(table).WherePri(conditions).Order("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 3)
gtest.Assert(result[0]["id"].Int(), 1)
})
// struct
gtest.Case(t, func() {
type User struct {
Id int `json:"id"`
Nickname string `gconv:"nickname"`
}
result, err := db.Table(table).WherePri(User{3, "name_3"}).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
result, err = db.Table(table).WherePri(&User{3, "name_3"}).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
// slice single
gtest.Case(t, func() {
result, err := db.Table(table).WherePri("id IN(?)", g.Slice{1, 3}).Order("id ASC").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 2)
gtest.Assert(result[0]["id"].Int(), 1)
gtest.Assert(result[1]["id"].Int(), 3)
})
// slice + string
gtest.Case(t, func() {
result, err := db.Table(table).WherePri("nickname=? AND id IN(?)", "name_3", g.Slice{1, 3}).Order("id ASC").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 1)
gtest.Assert(result[0]["id"].Int(), 3)
})
// slice + map
gtest.Case(t, func() {
result, err := db.Table(table).WherePri(g.Map{
"id": g.Slice{1, 3},
"nickname": "name_3",
}).Order("id ASC").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 1)
gtest.Assert(result[0]["id"].Int(), 3)
})
// slice + struct
gtest.Case(t, func() {
type User struct {
Ids []int `json:"id"`
Nickname string `gconv:"nickname"`
}
result, err := db.Table(table).WherePri(User{
Ids: []int{1, 3},
Nickname: "name_3",
}).Order("id ASC").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 1)
gtest.Assert(result[0]["id"].Int(), 3)
@ -969,7 +1350,7 @@ func Test_Model_Offset(t *testing.T) {
table := createInitTable()
defer dropTable(table)
result, err := db.Table(table).Limit(2).Offset(5).OrderBy("id").Select()
result, err := db.Table(table).Limit(2).Offset(5).Order("id").Select()
gtest.Assert(err, nil)
gtest.Assert(len(result), 2)
gtest.Assert(result[0]["id"], 6)
@ -980,7 +1361,7 @@ func Test_Model_ForPage(t *testing.T) {
table := createInitTable()
defer dropTable(table)
result, err := db.Table(table).ForPage(3, 3).OrderBy("id").Select()
result, err := db.Table(table).ForPage(3, 3).Order("id").Select()
gtest.Assert(err, nil)
gtest.Assert(len(result), 3)
gtest.Assert(result[0]["id"], 7)
@ -1153,7 +1534,7 @@ func Test_Model_Option_List(t *testing.T) {
gtest.Assert(err, nil)
n, _ := r.RowsAffected()
gtest.Assert(n, 2)
list, err := db.Table(table).OrderBy("id asc").All()
list, err := db.Table(table).Order("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(list), 2)
gtest.Assert(list[0]["id"].String(), "1")
@ -1187,7 +1568,7 @@ func Test_Model_Option_List(t *testing.T) {
gtest.Assert(err, nil)
n, _ := r.RowsAffected()
gtest.Assert(n, 2)
list, err := db.Table(table).OrderBy("id asc").All()
list, err := db.Table(table).Order("id asc").All()
g.Dump(list)
gtest.Assert(err, nil)
gtest.Assert(len(list), 2)
@ -1232,7 +1613,7 @@ func Test_Model_FieldsEx(t *testing.T) {
defer dropTable(table)
// Select.
gtest.Case(t, func() {
r, err := db.Table(table).FieldsEx("create_time, id").Where("id in (?)", g.Slice{1, 2}).OrderBy("id asc").All()
r, err := db.Table(table).FieldsEx("create_time, id").Where("id in (?)", g.Slice{1, 2}).Order("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(r), 2)
gtest.Assert(len(r[0]), 3)
@ -1260,3 +1641,34 @@ func Test_Model_FieldsEx(t *testing.T) {
gtest.AssertNE(one["password"], "456")
})
}
func Test_Model_Prefix(t *testing.T) {
db := dbPrefix
table := fmt.Sprintf(`%s_%d`, TABLE, gtime.Nanosecond())
createInitTableWithDb(db, PREFIX1+table)
defer dropTable(PREFIX1 + table)
// Select.
gtest.Case(t, func() {
r, err := db.Table(table).Where("id in (?)", g.Slice{1, 2}).Order("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(r), 2)
gtest.Assert(r[0]["id"], "1")
gtest.Assert(r[1]["id"], "2")
})
// Select with alias.
gtest.Case(t, func() {
r, err := db.Table(table+" as u").Where("u.id in (?)", g.Slice{1, 2}).Order("u.id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(r), 2)
gtest.Assert(r[0]["id"], "1")
gtest.Assert(r[1]["id"], "2")
})
// Select with alias and join statement.
gtest.Case(t, func() {
r, err := db.Table(table+" as u1").LeftJoin(table+" as u2", "u2.id=u1.id").Where("u1.id in (?)", g.Slice{1, 2}).Order("u1.id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(r), 2)
gtest.Assert(r[0]["id"], "1")
gtest.Assert(r[1]["id"], "2")
})
}

View File

@ -20,13 +20,6 @@ func Encode(src []byte) []byte {
return dst
}
// Decode decodes bytes with BASE64 algorithm.
func Decode(dst []byte) ([]byte, error) {
src := make([]byte, base64.StdEncoding.DecodedLen(len(dst)))
n, err := base64.StdEncoding.Decode(src, dst)
return src[:n], err
}
// EncodeString encodes string with BASE64 algorithm.
func EncodeString(src string) string {
return EncodeToString([]byte(src))
@ -46,6 +39,16 @@ func EncodeFile(path string) ([]byte, error) {
return Encode(content), nil
}
// MustEncodeFile encodes file content of <path> using BASE64 algorithms.
// It panics if any error occurs.
func MustEncodeFile(path string) []byte {
result, err := EncodeFile(path)
if err != nil {
panic(err)
}
return result
}
// EncodeFileToString encodes file content of <path> to string using BASE64 algorithms.
func EncodeFileToString(path string) (string, error) {
content, err := EncodeFile(path)
@ -55,13 +58,60 @@ func EncodeFileToString(path string) (string, error) {
return gconv.UnsafeBytesToStr(content), nil
}
// DecodeString decodes string with BASE64 algorithm.
func DecodeString(str string) ([]byte, error) {
return Decode([]byte(str))
// MustEncodeFileToString encodes file content of <path> to string using BASE64 algorithms.
// It panics if any error occurs.
func MustEncodeFileToString(path string) string {
result, err := EncodeFileToString(path)
if err != nil {
panic(err)
}
return result
}
// Decode decodes bytes with BASE64 algorithm.
func Decode(data []byte) ([]byte, error) {
src := make([]byte, base64.StdEncoding.DecodedLen(len(data)))
n, err := base64.StdEncoding.Decode(src, data)
return src[:n], err
}
// MustDecode decodes bytes with BASE64 algorithm.
// It panics if any error occurs.
func MustDecode(data []byte) []byte {
result, err := Decode(data)
if err != nil {
panic(err)
}
return result
}
// DecodeString decodes string with BASE64 algorithm.
func DecodeToString(str string) (string, error) {
b, err := DecodeString(str)
func DecodeString(data string) ([]byte, error) {
return Decode([]byte(data))
}
// MustDecodeString decodes string with BASE64 algorithm.
// It panics if any error occurs.
func MustDecodeString(data string) []byte {
result, err := DecodeString(data)
if err != nil {
panic(err)
}
return result
}
// DecodeString decodes string with BASE64 algorithm.
func DecodeToString(data string) (string, error) {
b, err := DecodeString(data)
return gconv.UnsafeBytesToStr(b), err
}
// MustDecodeToString decodes string with BASE64 algorithm.
// It panics if any error occurs.
func MustDecodeToString(data string) string {
result, err := DecodeToString(data)
if err != nil {
panic(err)
}
return result
}

View File

@ -78,7 +78,7 @@ func (j *Json) GetVars(pattern string, def ...interface{}) []*gvar.Var {
return gvar.New(j.Get(pattern, def...)).Vars()
}
// GetMap gets the value by specified <pattern>,
// GetMap retrieves the value by specified <pattern>,
// and converts it to map[string]interface{}.
func (j *Json) GetMap(pattern string, def ...interface{}) map[string]interface{} {
result := j.Get(pattern, def...)
@ -88,6 +88,16 @@ func (j *Json) GetMap(pattern string, def ...interface{}) map[string]interface{}
return nil
}
// GetMapStrStr retrieves the value by specified <pattern>,
// and converts it to map[string]string.
func (j *Json) GetMapStrStr(pattern string, def ...interface{}) map[string]string {
result := j.Get(pattern, def...)
if result != nil {
return gconv.MapStrStr(result)
}
return nil
}
// GetJson gets the value by specified <pattern>,
// and converts it to a un-concurrent-safe Json object.
func (j *Json) GetJson(pattern string, def ...interface{}) *Json {

View File

@ -12,25 +12,12 @@ import (
"github.com/gogf/gf/encoding/gtoml"
"github.com/gogf/gf/encoding/gxml"
"github.com/gogf/gf/encoding/gyaml"
"github.com/gogf/gf/util/gconv"
)
func (j *Json) ToXml(rootTag ...string) ([]byte, error) {
return gxml.Encode(j.ToMap(), rootTag...)
}
func (j *Json) ToXmlString(rootTag ...string) (string, error) {
b, e := j.ToXml(rootTag...)
return string(b), e
}
func (j *Json) ToXmlIndent(rootTag ...string) ([]byte, error) {
return gxml.EncodeWithIndent(j.ToMap(), rootTag...)
}
func (j *Json) ToXmlIndentString(rootTag ...string) (string, error) {
b, e := j.ToXmlIndent(rootTag...)
return string(b), e
}
// ========================================================================
// JSON
// ========================================================================
func (j *Json) ToJson() ([]byte, error) {
j.mu.RLock()
@ -54,6 +41,80 @@ func (j *Json) ToJsonIndentString() (string, error) {
return string(b), e
}
func (j *Json) MustToJson() []byte {
result, err := j.ToJson()
if err != nil {
panic(err)
}
return result
}
func (j *Json) MustToJsonString() string {
return gconv.UnsafeBytesToStr(j.MustToJson())
}
func (j *Json) MustToJsonIndent() []byte {
result, err := j.ToJsonIndent()
if err != nil {
panic(err)
}
return result
}
func (j *Json) MustToJsonIndentString() string {
return gconv.UnsafeBytesToStr(j.MustToJsonIndent())
}
// ========================================================================
// XML
// ========================================================================
func (j *Json) ToXml(rootTag ...string) ([]byte, error) {
return gxml.Encode(j.ToMap(), rootTag...)
}
func (j *Json) ToXmlString(rootTag ...string) (string, error) {
b, e := j.ToXml(rootTag...)
return string(b), e
}
func (j *Json) ToXmlIndent(rootTag ...string) ([]byte, error) {
return gxml.EncodeWithIndent(j.ToMap(), rootTag...)
}
func (j *Json) ToXmlIndentString(rootTag ...string) (string, error) {
b, e := j.ToXmlIndent(rootTag...)
return string(b), e
}
func (j *Json) MustToXml(rootTag ...string) []byte {
result, err := j.ToXml(rootTag...)
if err != nil {
panic(err)
}
return result
}
func (j *Json) MustToXmlString(rootTag ...string) string {
return gconv.UnsafeBytesToStr(j.MustToXml(rootTag...))
}
func (j *Json) MustToXmlIndent(rootTag ...string) []byte {
result, err := j.ToXmlIndent(rootTag...)
if err != nil {
panic(err)
}
return result
}
func (j *Json) MustToXmlIndentString(rootTag ...string) string {
return gconv.UnsafeBytesToStr(j.MustToXmlIndent(rootTag...))
}
// ========================================================================
// YAML
// ========================================================================
func (j *Json) ToYaml() ([]byte, error) {
j.mu.RLock()
defer j.mu.RUnlock()
@ -65,6 +126,22 @@ func (j *Json) ToYamlString() (string, error) {
return string(b), e
}
func (j *Json) MustToYaml() []byte {
result, err := j.ToYaml()
if err != nil {
panic(err)
}
return result
}
func (j *Json) MustToYamlString() string {
return gconv.UnsafeBytesToStr(j.MustToYaml())
}
// ========================================================================
// TOML
// ========================================================================
func (j *Json) ToToml() ([]byte, error) {
j.mu.RLock()
defer j.mu.RUnlock()
@ -76,6 +153,22 @@ func (j *Json) ToTomlString() (string, error) {
return string(b), e
}
func (j *Json) MustToToml() []byte {
result, err := j.ToToml()
if err != nil {
panic(err)
}
return result
}
func (j *Json) MustToTomlString() string {
return gconv.UnsafeBytesToStr(j.MustToToml())
}
// ========================================================================
// INI
// ========================================================================
func (j *Json) ToIni() ([]byte, error) {
j.mu.RLock()
defer j.mu.RUnlock()
@ -86,3 +179,15 @@ func (j *Json) ToIniString() (string, error) {
b, e := j.ToToml()
return string(b), e
}
func (j *Json) MustToIni() []byte {
result, err := j.ToIni()
if err != nil {
panic(err)
}
return result
}
func (j *Json) MustToIniString() string {
return gconv.UnsafeBytesToStr(j.MustToIni())
}

View File

@ -25,11 +25,10 @@ import (
"github.com/gogf/gf/util/gconv"
)
// New creates a Json object with any variable type of <data>,
// but <data> should be a map or slice for data access reason,
// or it will make no sense.
// The <unsafe> param specifies whether using this Json object
// in un-concurrent-safe context, which is false in default.
// New creates a Json object with any variable type of <data>, but <data> should be a map or slice
// for data access reason, or it will make no sense.
// The <safe> param specifies whether using this Json object in concurrent-safe context, which is
// false in default.
func New(data interface{}, safe ...bool) *Json {
j := (*Json)(nil)
switch data.(type) {
@ -61,7 +60,8 @@ func New(data interface{}, safe ...bool) *Json {
}
case reflect.Map, reflect.Struct:
i := interface{}(nil)
i = gconv.Map(data, "json")
// Note that it uses MapDeep function implementing the converting.
i = gconv.MapDeep(data, "json")
j = &Json{
p: &i,
c: byte(gDEFAULT_SPLIT_CHAR),

View File

@ -6,13 +6,9 @@
package gparser
func VarToXml(value interface{}, rootTag ...string) ([]byte, error) {
return New(value).ToXml(rootTag...)
}
func VarToXmlIndent(value interface{}, rootTag ...string) ([]byte, error) {
return New(value).ToXmlIndent(rootTag...)
}
// ========================================================================
// JSON
// ========================================================================
func VarToJson(value interface{}) ([]byte, error) {
return New(value).ToJson()
@ -30,14 +26,114 @@ func VarToJsonIndentString(value interface{}) (string, error) {
return New(value).ToJsonIndentString()
}
func MustToJson(value interface{}) []byte {
return New(value).MustToJson()
}
func MustToJsonString(value interface{}) string {
return New(value).MustToJsonString()
}
func MustToJsonIndent(value interface{}) []byte {
return New(value).MustToJsonIndent()
}
func MustToJsonIndentString(value interface{}) string {
return New(value).MustToJsonIndentString()
}
// ========================================================================
// XML
// ========================================================================
func VarToXml(value interface{}, rootTag ...string) ([]byte, error) {
return New(value).ToXml(rootTag...)
}
func VarToXmlString(value interface{}, rootTag ...string) (string, error) {
return New(value).ToXmlString(rootTag...)
}
func VarToXmlIndent(value interface{}, rootTag ...string) ([]byte, error) {
return New(value).ToXmlIndent(rootTag...)
}
func VarToXmlIndentString(value interface{}, rootTag ...string) (string, error) {
return New(value).ToXmlIndentString(rootTag...)
}
func MustToXml(value interface{}, rootTag ...string) []byte {
return New(value).MustToXml(rootTag...)
}
func MustToXmlString(value interface{}, rootTag ...string) string {
return New(value).MustToXmlString(rootTag...)
}
func MustToXmlIndent(value interface{}, rootTag ...string) []byte {
return New(value).MustToXmlIndent(rootTag...)
}
func MustToXmlIndentString(value interface{}, rootTag ...string) string {
return New(value).MustToXmlIndentString(rootTag...)
}
// ========================================================================
// YAML
// ========================================================================
func VarToYaml(value interface{}) ([]byte, error) {
return New(value).ToYaml()
}
func VarToYamlString(value interface{}) (string, error) {
return New(value).ToYamlString()
}
func MustToYaml(value interface{}) []byte {
return New(value).MustToYaml()
}
func MustToYamlString(value interface{}) string {
return New(value).MustToYamlString()
}
// ========================================================================
// TOML
// ========================================================================
func VarToToml(value interface{}) ([]byte, error) {
return New(value).ToToml()
}
func VarToTomlString(value interface{}) (string, error) {
return New(value).ToTomlString()
}
func MustToToml(value interface{}) []byte {
return New(value).MustToToml()
}
func MustToTomlString(value interface{}) string {
return New(value).MustToTomlString()
}
// ========================================================================
// INI
// ========================================================================
func VarToIni(value interface{}) ([]byte, error) {
return New(value).ToIni()
}
func VarToIniString(value interface{}) (string, error) {
return New(value).ToIniString()
}
func MustToIni(value interface{}) []byte {
return New(value).MustToIni()
}
func MustToIniString(value interface{}) string {
return New(value).MustToIniString()
}

View File

@ -5,12 +5,16 @@
// You can obtain one at https://github.com/gogf/gf.
// Package gins provides instances and core components management.
//
// Note that it should not used glog.Panic* functions for panics if you do not want
// to log all the panics.
package gins
import (
"fmt"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/util/gutil"
"github.com/gogf/gf/os/gfile"
@ -102,7 +106,7 @@ func View(name ...string) *gview.View {
}
if m != nil {
if err := view.SetConfigWithMap(m); err != nil {
glog.Panic(err)
panic(err)
}
}
}
@ -149,7 +153,7 @@ func Log(name ...string) *glog.Logger {
}
if m != nil {
if err := logger.SetConfigWithMap(m); err != nil {
glog.Panic(err)
panic(err)
}
}
}
@ -167,18 +171,19 @@ func Database(name ...string) gdb.DB {
}
instanceKey := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_DATABASE, group)
db := instances.GetOrSetFuncLock(instanceKey, func() interface{} {
// Configuration already exists.
if gdb.GetConfig(group) != nil {
db, err := gdb.Instance(group)
if err != nil {
glog.Panic(err)
panic(err)
}
return db
}
m := config.GetMap("database")
if m == nil {
glog.Panic(`database init failed: "database" node not found, is config file or configuration missing?`)
panic(`database init failed: "database" node not found, is config file or configuration missing?`)
}
// Parse <m> as map-slice.
// Parse <m> as map-slice and adds it to gdb's global configurations.
for group, groupConfig := range m {
cg := gdb.ConfigGroup{}
switch value := groupConfig.(type) {
@ -197,14 +202,15 @@ func Database(name ...string) gdb.DB {
gdb.SetConfigGroup(group, cg)
}
}
// Parse <m> as a single node configuration.
// Parse <m> as a single node configuration,
// which is the default group configuration.
if node := parseDBConfigNode(m); node != nil {
cg := gdb.ConfigGroup{}
if node.LinkInfo != "" || node.Host != "" {
cg = append(cg, *node)
}
if len(cg) > 0 {
gdb.SetConfigGroup(group, cg)
gdb.SetConfigGroup(gdb.DEFAULT_GROUP_NAME, cg)
}
}
addConfigMonitor(instanceKey, config)
@ -217,12 +223,12 @@ func Database(name ...string) gdb.DB {
}
if m != nil {
if err := db.GetLogger().SetConfigWithMap(m); err != nil {
glog.Panic(err)
panic(err)
}
}
return db
} else {
glog.Panic(err)
panic(err)
}
return nil
})
@ -240,10 +246,10 @@ func parseDBConfigNode(value interface{}) *gdb.ConfigNode {
node := &gdb.ConfigNode{}
err := gconv.Struct(nodeMap, node)
if err != nil {
glog.Panic(err)
panic(err)
}
if value, ok := nodeMap["link"]; ok {
node.LinkInfo = gconv.String(value)
if _, v := gutil.MapPossibleItemByKey(nodeMap, "link"); v != nil {
node.LinkInfo = gconv.String(v)
}
// Parse link syntax.
if node.LinkInfo != "" && node.Type == "" {
@ -280,15 +286,15 @@ func Redis(name ...string) *gredis.Redis {
if v, ok := m[group]; ok {
redisConfig, err := gredis.ConfigFromStr(gconv.String(v))
if err != nil {
glog.Panic(err)
panic(err)
}
addConfigMonitor(instanceKey, config)
return gredis.New(redisConfig)
} else {
glog.Panicf(`configuration for redis not found for group "%s"`, group)
panic(fmt.Sprintf(`configuration for redis not found for group "%s"`, group))
}
} else {
glog.Panicf(`incomplete configuration for redis: "redis" node not found in config file "%s"`, config.FilePath())
panic(fmt.Sprintf(`incomplete configuration for redis: "redis" node not found in config file "%s"`, config.FilePath()))
}
return nil
})

View File

@ -6,6 +6,6 @@
package gmvc
// MVC模型基类
// Model is the base struct for model.
type Model struct {
}

View File

@ -7,6 +7,7 @@
package gmvc
import (
"github.com/gogf/gf/frame/gins"
"sync"
"github.com/gogf/gf/util/gmode"
@ -26,7 +27,7 @@ type View struct {
// 创建一个MVC请求中使用的视图对象
func NewView(w *ghttp.Response) *View {
return &View{
view: gview.New(),
view: gins.View(),
data: make(gview.Params),
response: w,
}

View File

@ -7,9 +7,12 @@
// Package structs provides functions for struct conversion.
package structs
import (
"github.com/fatih/structs"
)
import "github.com/fatih/structs"
// Field is alias of structs.Field.
type Field = structs.Field
type Field struct {
*structs.Field
// Retrieved tag name. There might be more than one tags in the field,
// but only one can be retrieved according to calling function rules.
Tag string
}

View File

@ -33,7 +33,10 @@ func MapField(pointer interface{}, priority []string, recursive bool) map[string
if name[0] < byte('A') || name[0] > byte('Z') {
continue
}
fieldMap[name] = field
fieldMap[name] = &Field{
Field: field,
Tag: tag,
}
tag = ""
for _, p := range priority {
tag = field.Tag(p)
@ -42,7 +45,10 @@ func MapField(pointer interface{}, priority []string, recursive bool) map[string
}
}
if tag != "" {
fieldMap[tag] = field
fieldMap[tag] = &Field{
Field: field,
Tag: tag,
}
}
if recursive {
rv := reflect.ValueOf(field.Value())

View File

@ -12,31 +12,19 @@ import (
"github.com/fatih/structs"
)
// TagMapName retrieves struct tags as map[tag]attribute from <pointer>, and returns it.
// TagFields retrieves struct tags as []*Field from <pointer>, and returns it.
//
// The parameter <recursive> specifies whether retrieving the struct field recursively.
//
// Note that it only retrieves the exported attributes with first letter up-case from struct.
func TagMapName(pointer interface{}, priority []string, recursive bool) map[string]string {
tagMap := TagMapField(pointer, priority, recursive)
if len(tagMap) > 0 {
m := make(map[string]string, len(tagMap))
for k, v := range tagMap {
m[k] = v.Name()
}
return m
}
return nil
func TagFields(pointer interface{}, priority []string, recursive bool) []*Field {
return doTagFields(pointer, priority, recursive, map[string]struct{}{})
}
// TagMapField retrieves struct tags as map[tag]*Field from <pointer>, and returns it.
//
// The parameter <recursive> specifies whether retrieving the struct field recursively.
//
// Note that it only retrieves the exported attributes with first letter up-case from struct.
func TagMapField(pointer interface{}, priority []string, recursive bool) map[string]*Field {
tagMap := make(map[string]*Field)
fields := ([]*structs.Field)(nil)
// doTagFields retrieves the tag and corresponding attribute name from <pointer>. It also filters repeated
// tag internally.
func doTagFields(pointer interface{}, priority []string, recursive bool, tagMap map[string]struct{}) []*Field {
var fields []*structs.Field
if v, ok := pointer.(reflect.Value); ok {
fields = structs.Fields(v.Interface())
} else {
@ -56,13 +44,13 @@ func TagMapField(pointer interface{}, priority []string, recursive bool) map[str
}
tag := ""
name := ""
tagFields := make([]*Field, 0)
for _, field := range fields {
name = field.Name()
// Only retrieve exported attributes.
if name[0] < byte('A') || name[0] > byte('Z') {
continue
}
tag = ""
for _, p := range priority {
tag = field.Tag(p)
@ -71,7 +59,14 @@ func TagMapField(pointer interface{}, priority []string, recursive bool) map[str
}
}
if tag != "" {
tagMap[tag] = field
// Filter repeated tag.
if _, ok := tagMap[tag]; ok {
continue
}
tagFields = append(tagFields, &Field{
Field: field,
Tag: tag,
})
}
if recursive {
rv := reflect.ValueOf(field.Value())
@ -81,13 +76,37 @@ func TagMapField(pointer interface{}, priority []string, recursive bool) map[str
kind = rv.Kind()
}
if kind == reflect.Struct {
for k, v := range TagMapField(rv, priority, true) {
if _, ok := tagMap[k]; !ok {
tagMap[k] = v
}
}
tagFields = append(tagFields, doTagFields(rv, priority, recursive, tagMap)...)
}
}
}
return tagFields
}
// TagMapName retrieves struct tags as map[tag]attribute from <pointer>, and returns it.
//
// The parameter <recursive> specifies whether retrieving the struct field recursively.
//
// Note that it only retrieves the exported attributes with first letter up-case from struct.
func TagMapName(pointer interface{}, priority []string, recursive bool) map[string]string {
fields := TagFields(pointer, priority, recursive)
tagMap := make(map[string]string, len(fields))
for _, v := range fields {
tagMap[v.Tag] = v.Name()
}
return tagMap
}
// TagMapField retrieves struct tags as map[tag]*Field from <pointer>, and returns it.
//
// The parameter <recursive> specifies whether retrieving the struct field recursively.
//
// Note that it only retrieves the exported attributes with first letter up-case from struct.
func TagMapField(pointer interface{}, priority []string, recursive bool) map[string]*Field {
fields := TagFields(pointer, priority, recursive)
tagMap := make(map[string]*Field, len(fields))
for _, v := range fields {
tagMap[v.Tag] = v
}
return tagMap
}

View File

@ -8,5 +8,7 @@
package ghttp
var (
// paramTagPriority is the priority tag array for request parameter
// to struct field mapping.
paramTagPriority = []string{"param", "params", "p"}
)

View File

@ -6,122 +6,182 @@
package ghttp
// Get is a convenience method for sending GET request.
// NOTE that remembers CLOSING the response object when it'll never be used.
func Get(url string) (*ClientResponse, error) {
return DoRequest("GET", url)
}
// Put is a convenience method for sending PUT request.
// NOTE that remembers CLOSING the response object when it'll never be used.
func Put(url string, data ...interface{}) (*ClientResponse, error) {
return DoRequest("PUT", url, data...)
}
// Post is a convenience method for sending POST request.
// NOTE that remembers CLOSING the response object when it'll never be used.
func Post(url string, data ...interface{}) (*ClientResponse, error) {
return DoRequest("POST", url, data...)
}
// Delete is a convenience method for sending DELETE request.
// NOTE that remembers CLOSING the response object when it'll never be used.
func Delete(url string, data ...interface{}) (*ClientResponse, error) {
return DoRequest("DELETE", url, data...)
}
// Head is a convenience method for sending HEAD request.
// NOTE that remembers CLOSING the response object when it'll never be used.
func Head(url string, data ...interface{}) (*ClientResponse, error) {
return DoRequest("HEAD", url, data...)
}
// Patch is a convenience method for sending PATCH request.
// NOTE that remembers CLOSING the response object when it'll never be used.
func Patch(url string, data ...interface{}) (*ClientResponse, error) {
return DoRequest("PATCH", url, data...)
}
// Connect is a convenience method for sending CONNECT request.
// NOTE that remembers CLOSING the response object when it'll never be used.
func Connect(url string, data ...interface{}) (*ClientResponse, error) {
return DoRequest("CONNECT", url, data...)
}
// Options is a convenience method for sending OPTIONS request.
// NOTE that remembers CLOSING the response object when it'll never be used.
func Options(url string, data ...interface{}) (*ClientResponse, error) {
return DoRequest("OPTIONS", url, data...)
}
// Trace is a convenience method for sending TRACE request.
// NOTE that remembers CLOSING the response object when it'll never be used.
func Trace(url string, data ...interface{}) (*ClientResponse, error) {
return DoRequest("TRACE", url, data...)
}
// DoRequest is a convenience method for sending custom http method request.
// NOTE that remembers CLOSING the response object when it'll never be used.
func DoRequest(method, url string, data ...interface{}) (*ClientResponse, error) {
return NewClient().DoRequest(method, url, data...)
}
// GetContent is a convenience method for sending GET request, which retrieves and returns
// the result content and automatically closes response object.
func GetContent(url string, data ...interface{}) string {
return RequestContent("GET", url, data...)
}
// PutContent is a convenience method for sending PUT request, which retrieves and returns
// the result content and automatically closes response object.
func PutContent(url string, data ...interface{}) string {
return RequestContent("PUT", url, data...)
}
// PostContent is a convenience method for sending POST request, which retrieves and returns
// the result content and automatically closes response object.
func PostContent(url string, data ...interface{}) string {
return RequestContent("POST", url, data...)
}
// DeleteContent is a convenience method for sending DELETE request, which retrieves and returns
// the result content and automatically closes response object.
func DeleteContent(url string, data ...interface{}) string {
return RequestContent("DELETE", url, data...)
}
// HeadContent is a convenience method for sending HEAD request, which retrieves and returns
// the result content and automatically closes response object.
func HeadContent(url string, data ...interface{}) string {
return RequestContent("HEAD", url, data...)
}
// PatchContent is a convenience method for sending PATCH request, which retrieves and returns
// the result content and automatically closes response object.
func PatchContent(url string, data ...interface{}) string {
return RequestContent("PATCH", url, data...)
}
// ConnectContent is a convenience method for sending CONNECT request, which retrieves and returns
// the result content and automatically closes response object.
func ConnectContent(url string, data ...interface{}) string {
return RequestContent("CONNECT", url, data...)
}
// OptionsContent is a convenience method for sending OPTIONS request, which retrieves and returns
// the result content and automatically closes response object.
func OptionsContent(url string, data ...interface{}) string {
return RequestContent("OPTIONS", url, data...)
}
// TraceContent is a convenience method for sending TRACE request, which retrieves and returns
// the result content and automatically closes response object.
func TraceContent(url string, data ...interface{}) string {
return RequestContent("TRACE", url, data...)
}
// RequestContent is a convenience method for sending custom http method request, which
// retrieves and returns the result content and automatically closes response object.
func RequestContent(method string, url string, data ...interface{}) string {
return NewClient().RequestContent(method, url, data...)
}
// GetBytes is a convenience method for sending GET request, which retrieves and returns
// the result content as bytes and automatically closes response object.
func GetBytes(url string, data ...interface{}) []byte {
return RequestBytes("GET", url, data...)
}
// PutBytes is a convenience method for sending PUT request, which retrieves and returns
// the result content as bytes and automatically closes response object.
func PutBytes(url string, data ...interface{}) []byte {
return RequestBytes("PUT", url, data...)
}
// PostBytes is a convenience method for sending POST request, which retrieves and returns
// the result content as bytes and automatically closes response object.
func PostBytes(url string, data ...interface{}) []byte {
return RequestBytes("POST", url, data...)
}
// DeleteBytes is a convenience method for sending DELETE request, which retrieves and returns
// the result content as bytes and automatically closes response object.
func DeleteBytes(url string, data ...interface{}) []byte {
return RequestBytes("DELETE", url, data...)
}
// HeadBytes is a convenience method for sending HEAD request, which retrieves and returns
// the result content as bytes and automatically closes response object.
func HeadBytes(url string, data ...interface{}) []byte {
return RequestBytes("HEAD", url, data...)
}
// PatchBytes is a convenience method for sending PATCH request, which retrieves and returns
// the result content as bytes and automatically closes response object.
func PatchBytes(url string, data ...interface{}) []byte {
return RequestBytes("PATCH", url, data...)
}
// ConnectBytes is a convenience method for sending CONNECT request, which retrieves and returns
// the result content as bytes and automatically closes response object.
func ConnectBytes(url string, data ...interface{}) []byte {
return RequestBytes("CONNECT", url, data...)
}
// OptionsBytes is a convenience method for sending OPTIONS request, which retrieves and returns
// the result content as bytes and automatically closes response object.
func OptionsBytes(url string, data ...interface{}) []byte {
return RequestBytes("OPTIONS", url, data...)
}
// TraceBytes is a convenience method for sending TRACE request, which retrieves and returns
// the result content as bytes and automatically closes response object.
func TraceBytes(url string, data ...interface{}) []byte {
return RequestBytes("TRACE", url, data...)
}
// RequestBytes is a convenience method for sending custom http method request, which
// retrieves and returns the result content as bytes and automatically closes response object.
func RequestBytes(method string, url string, data ...interface{}) []byte {
return NewClient().RequestBytes(method, url, data...)
}

View File

@ -6,44 +6,53 @@
package ghttp
// GetBytes sends a GET request, retrieves and returns the result content as bytes.
func (c *Client) GetBytes(url string, data ...interface{}) []byte {
return c.RequestBytes("GET", url, data...)
}
// PutBytes sends a PUT request, retrieves and returns the result content as bytes.
func (c *Client) PutBytes(url string, data ...interface{}) []byte {
return c.RequestBytes("PUT", url, data...)
}
// PostBytes sends a POST request, retrieves and returns the result content as bytes.
func (c *Client) PostBytes(url string, data ...interface{}) []byte {
return c.RequestBytes("POST", url, data...)
}
// DeleteBytes sends a DELETE request, retrieves and returns the result content as bytes.
func (c *Client) DeleteBytes(url string, data ...interface{}) []byte {
return c.RequestBytes("DELETE", url, data...)
}
// HeadBytes sends a HEAD request, retrieves and returns the result content as bytes.
func (c *Client) HeadBytes(url string, data ...interface{}) []byte {
return c.RequestBytes("HEAD", url, data...)
}
// PatchBytes sends a PATCH request, retrieves and returns the result content as bytes.
func (c *Client) PatchBytes(url string, data ...interface{}) []byte {
return c.RequestBytes("PATCH", url, data...)
}
// ConnectBytes sends a CONNECT request, retrieves and returns the result content as bytes.
func (c *Client) ConnectBytes(url string, data ...interface{}) []byte {
return c.RequestBytes("CONNECT", url, data...)
}
// OptionsBytes sends a OPTIONS request, retrieves and returns the result content as bytes.
func (c *Client) OptionsBytes(url string, data ...interface{}) []byte {
return c.RequestBytes("OPTIONS", url, data...)
}
// TraceBytes sends a TRACE request, retrieves and returns the result content as bytes.
func (c *Client) TraceBytes(url string, data ...interface{}) []byte {
return c.RequestBytes("TRACE", url, data...)
}
// RequestBytes sends request using given HTTP method and data and returns the binary as bytes.
// It reads and closes the response object internally automatically.
// RequestBytes sends request using given HTTP method and data, retrieves returns the result
// as bytes. It reads and closes the response object internally automatically.
func (c *Client) RequestBytes(method string, url string, data ...interface{}) []byte {
response, err := c.DoRequest(method, url, data...)
if err != nil {

View File

@ -46,7 +46,7 @@ type Request struct {
clientIp string // The parsed client ip for current host used by GetClientIp function.
bodyContent []byte // Request body content.
isFileRequest bool // A bool marking whether current request is file serving.
view *gview.View // Custom template view engine object for this response.
viewObject *gview.View // Custom template view engine object for this response.
viewParams gview.Params // Custom template view variables for this response.
}

View File

@ -128,8 +128,15 @@ func (r *Request) GetMapStrStr(def ...map[string]interface{}) map[string]string
return r.GetRequestMapStrStr(def...)
}
// GetStruct is alias of GetRequestToStruct.
// See GetRequestToStruct.
func (r *Request) GetStruct(pointer interface{}, mapping ...map[string]string) error {
return r.GetRequestToStruct(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...)
}

View File

@ -154,10 +154,10 @@ func (r *Request) GetFormMapStrVar(kvMap ...map[string]interface{}) map[string]*
return nil
}
// GetFormToStruct retrieves all form parameters passed from client and converts them to
// GetFormStruct retrieves all form parameters passed from client 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.
func (r *Request) GetFormToStruct(pointer interface{}, mapping ...map[string]string) error {
func (r *Request) GetFormStruct(pointer interface{}, mapping ...map[string]string) error {
r.ParseForm()
tagMap := structs.TagMapName(pointer, paramTagPriority, true)
if len(mapping) > 0 {
@ -167,3 +167,9 @@ func (r *Request) GetFormToStruct(pointer interface{}, mapping ...map[string]str
}
return gconv.StructDeep(r.formMap, pointer, tagMap)
}
// GetFormToStruct is alias of GetFormStruct. See GetFormStruct.
// Deprecated.
func (r *Request) GetFormToStruct(pointer interface{}, mapping ...map[string]string) error {
return r.GetFormStruct(pointer, mapping...)
}

View File

@ -174,11 +174,11 @@ func (r *Request) GetPostMapStrVar(kvMap ...map[string]interface{}) map[string]*
return nil
}
// GetPostToStruct retrieves all parameters in the form and body passed from client
// GetPostStruct retrieves all parameters in the form and body passed from client
// 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.
func (r *Request) GetPostToStruct(pointer interface{}, mapping ...map[string]string) error {
func (r *Request) GetPostStruct(pointer interface{}, mapping ...map[string]string) error {
tagMap := structs.TagMapName(pointer, paramTagPriority, true)
if len(mapping) > 0 {
for k, v := range mapping[0] {
@ -187,3 +187,9 @@ func (r *Request) GetPostToStruct(pointer interface{}, mapping ...map[string]str
}
return gconv.StructDeep(r.GetPostMap(), pointer, tagMap)
}
// 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

@ -188,11 +188,11 @@ func (r *Request) GetQueryMapStrVar(kvMap ...map[string]interface{}) map[string]
return nil
}
// GetQueryToStruct retrieves all parameters passed from client using HTTP GET method
// GetQueryStruct retrieves all parameters passed from client using HTTP GET method
// 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.
func (r *Request) GetQueryToStruct(pointer interface{}, mapping ...map[string]string) error {
func (r *Request) GetQueryStruct(pointer interface{}, mapping ...map[string]string) error {
r.ParseQuery()
tagMap := structs.TagMapName(pointer, paramTagPriority, true)
if len(mapping) > 0 {
@ -202,3 +202,9 @@ func (r *Request) GetQueryToStruct(pointer interface{}, mapping ...map[string]st
}
return gconv.StructDeep(r.GetQueryMap(), pointer, tagMap)
}
// GetQueryToStruct is alias of GetQueryStruct. See GetQueryStruct.
// Deprecated.
func (r *Request) GetQueryToStruct(pointer interface{}, mapping ...map[string]string) error {
return r.GetQueryStruct(pointer, mapping...)
}

View File

@ -263,10 +263,10 @@ func (r *Request) GetRequestMapStrVar(kvMap ...map[string]interface{}) map[strin
return nil
}
// GetRequestToStruct retrieves all parameters passed from client no matter what HTTP method the client is using,
// GetRequestStruct retrieves all parameters passed from client no matter what HTTP method the client is using,
// 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.
func (r *Request) GetRequestToStruct(pointer interface{}, mapping ...map[string]string) error {
func (r *Request) GetRequestStruct(pointer interface{}, mapping ...map[string]string) error {
tagMap := structs.TagMapName(pointer, paramTagPriority, true)
if len(mapping) > 0 {
for k, v := range mapping[0] {
@ -275,3 +275,9 @@ func (r *Request) GetRequestToStruct(pointer interface{}, mapping ...map[string]
}
return gconv.StructDeep(r.GetRequestMap(), pointer, tagMap)
}
// GetRequestToStruct is alias of GetRequestStruct. See GetRequestStruct.
// Deprecated.
func (r *Request) GetRequestToStruct(pointer interface{}, mapping ...map[string]string) error {
return r.GetRequestStruct(pointer, mapping...)
}

View File

@ -8,14 +8,14 @@ package ghttp
import "github.com/gogf/gf/os/gview"
// SetView sets template view engine object for this response.
// SetView sets template view engine object for this request.
func (r *Request) SetView(view *gview.View) {
r.view = view
r.viewObject = view
}
// GetView returns the template view engine object for this response.
// GetView returns the template view engine object for this request.
func (r *Request) GetView() *gview.View {
view := r.view
view := r.viewObject
if view == nil {
view = r.Server.config.View
}

View File

@ -91,8 +91,8 @@ func (r *Response) buildInVars(params ...map[string]interface{}) map[string]inte
if c := gcfg.Instance(); c.Available() {
vars["Config"] = c.GetMap(".")
}
vars["Get"] = r.Request.GetQueryMap()
vars["Post"] = r.Request.GetPostMap()
vars["Form"] = r.Request.GetFormMap()
vars["Query"] = r.Request.GetQueryMap()
vars["Cookie"] = r.Request.Cookie.Map()
vars["Session"] = r.Request.Session.Map()
return vars

View File

@ -63,12 +63,16 @@ type (
// Router item just for dumping.
RouterItem struct {
Middleware string
Domain string
Method string
Route string
Priority int
handler *handlerItem
Server string
Address string
Domain string
Type int
Middleware string
Method string
Route string
Priority int
IsServiceHandler bool
handler *handlerItem
}
// 路由函数注册信息
@ -317,77 +321,70 @@ func (s *Server) Start() error {
if gproc.IsChild() {
gtimer.SetTimeout(2*time.Second, func() {
if err := gproc.Send(gproc.PPid(), []byte("exit"), gADMIN_GPROC_COMM_GROUP); err != nil {
glog.Error("[ghttp] server error in process communication:", err)
//glog.Error("[ghttp] server error in process communication:", err)
}
})
}
s.DumpRouterMap()
s.dumpRouterMap()
return nil
}
// DumpRouterMap dumps the router map to the log.
func (s *Server) DumpRouterMap() {
func (s *Server) dumpRouterMap() {
if s.config.DumpRouterMap && len(s.routesMap) > 0 {
buffer := bytes.NewBuffer(nil)
table := tablewriter.NewWriter(buffer)
table.SetHeader([]string{"SERVER", "ADDRESS", "DOMAIN", "METHOD", "P", "ROUTE", "HANDLER", "MIDDLEWARE"})
table.SetHeader([]string{"SERVER", "DOMAIN", "ADDRESS", "METHOD", "ROUTE", "HANDLER", "MIDDLEWARE"})
table.SetRowLine(true)
table.SetBorder(false)
table.SetCenterSeparator("|")
table.SetColumnAlignment([]int{
tablewriter.ALIGN_CENTER,
tablewriter.ALIGN_CENTER,
tablewriter.ALIGN_CENTER,
tablewriter.ALIGN_CENTER,
tablewriter.ALIGN_CENTER,
tablewriter.ALIGN_LEFT,
tablewriter.ALIGN_LEFT,
tablewriter.ALIGN_LEFT,
})
address := s.config.Address
if s.config.HTTPSAddr != "" {
if len(address) > 0 {
address += ","
}
address += "tls" + s.config.HTTPSAddr
}
for _, array := range s.GetRouterMap() {
data := make([]string, 8)
for _, item := range array {
data[0] = s.name
data[1] = address
data[2] = item.Domain
data[3] = item.Method
data[4] = gconv.String(len(strings.Split(item.Route, "/")) - 1 + item.Priority)
data[5] = item.Route
data[6] = item.handler.itemName
data[7] = item.Middleware
table.Append(data)
}
for _, item := range s.GetRouterArray() {
data := make([]string, 7)
data[0] = item.Server
data[1] = item.Domain
data[2] = item.Address
data[3] = item.Method
data[4] = item.Route
data[5] = item.handler.itemName
data[6] = item.Middleware
table.Append(data)
}
table.Render()
s.config.Logger.Header(false).Printf("\n%s", buffer.String())
}
}
// GetRouterMap retrieves and returns the router map.
// GetRouterArray retrieves and returns the router array.
// The key of the returned map is the domain of the server.
func (s *Server) GetRouterMap() map[string][]RouterItem {
func (s *Server) GetRouterArray() []RouterItem {
m := make(map[string]*garray.SortedArray)
address := s.config.Address
if s.config.HTTPSAddr != "" {
if len(address) > 0 {
address += ","
}
address += "tls" + s.config.HTTPSAddr
}
for k, registeredItems := range s.routesMap {
array, _ := gregex.MatchString(`(.*?)%([A-Z]+):(.+)@(.+)`, k)
for index, registeredItem := range registeredItems {
item := RouterItem{
Middleware: array[1],
Server: s.name,
Address: address,
Domain: array[4],
Type: registeredItem.handler.itemType,
Middleware: array[1],
Method: array[2],
Route: array[3],
Priority: len(registeredItems) - index - 1,
handler: registeredItem.handler,
}
if item.handler.itemType == gHANDLER_TYPE_MIDDLEWARE {
switch item.handler.itemType {
case gHANDLER_TYPE_CONTROLLER, gHANDLER_TYPE_OBJECT, gHANDLER_TYPE_HANDLER:
item.IsServiceHandler = true
case gHANDLER_TYPE_MIDDLEWARE:
item.Middleware = "GLOBAL MIDDLEWARE"
}
if len(item.handler.middleware) > 0 {
@ -402,7 +399,7 @@ func (s *Server) GetRouterMap() map[string][]RouterItem {
// The value of the map is a custom sorted array.
if _, ok := m[item.Domain]; !ok {
// Sort in ASC order.
m[item.Domain] = garray.NewSortedArraySize(100, func(v1, v2 interface{}) int {
m[item.Domain] = garray.NewSortedArray(func(v1, v2 interface{}) int {
item1 := v1.(RouterItem)
item2 := v2.(RouterItem)
r := 0
@ -425,16 +422,13 @@ func (s *Server) GetRouterMap() map[string][]RouterItem {
m[item.Domain].Add(item)
}
}
routerMap := make(map[string][]RouterItem, len(m))
for domain, array := range m {
if routerMap[domain] == nil {
routerMap[domain] = make([]RouterItem, array.Len())
}
for k, v := range array.Slice() {
routerMap[domain][k] = v.(RouterItem)
routerArray := make([]RouterItem, 0, 128)
for _, array := range m {
for _, v := range array.Slice() {
routerArray = append(routerArray, v.(RouterItem))
}
}
return routerMap
return routerArray
}
// Run starts server listening in blocking way.

View File

@ -73,7 +73,9 @@ type ServerConfig struct {
PProfPattern string // PProf: PProf service pattern for router.
FormParsingMemory int64 // Other: Max memory in bytes which can be used for parsing multimedia form.
NameToUriType int // Other: Type for converting struct method name to URI when registering routes.
RouteOverWrite bool // Other: Allow overwrite the route if duplicated.
DumpRouterMap bool // Other: Whether automatically dump router map when server starts.
Graceful bool // Other: Enable graceful reload feature for all servers of the process.
}
// defaultServerConfig is the default configuration object for server.
@ -108,6 +110,7 @@ var defaultServerConfig = ServerConfig{
DumpRouterMap: true,
FormParsingMemory: 100 * 1024 * 1024, // 100MB
Rewrites: make(map[string]string),
Graceful: true,
}
// Config returns the default ServerConfig object.
@ -140,6 +143,7 @@ func (s *Server) SetConfig(c ServerConfig) error {
if c.TLSConfig == nil && c.HTTPSCertPath != "" {
s.EnableHTTPS(c.HTTPSCertPath, c.HTTPSKeyPath)
}
SetGraceful(c.Graceful)
return nil
}

View File

@ -15,3 +15,7 @@ func (s *Server) SetRewriteMap(rewrites map[string]string) {
s.config.Rewrites[k] = v
}
}
func (s *Server) SetRouteOverWrite(enabled bool) {
s.config.RouteOverWrite = enabled
}

View File

@ -28,7 +28,6 @@ func (s *Server) Domain(domains string) *Domain {
return d
}
// 注意该方法是直接绑定方法的内存地址,执行的时候直接执行该方法,不会存在初始化新的控制器逻辑
func (d *Domain) BindHandler(pattern string, handler HandlerFunc) {
for domain, _ := range d.m {
d.s.BindHandler(pattern+"@"+domain, handler)
@ -41,7 +40,6 @@ func (d *Domain) doBindHandler(pattern string, handler HandlerFunc, middleware [
}
}
// 执行对象方法
func (d *Domain) BindObject(pattern string, obj interface{}, methods ...string) {
for domain, _ := range d.m {
d.s.BindObject(pattern+"@"+domain, obj, methods...)
@ -54,7 +52,6 @@ func (d *Domain) doBindObject(pattern string, obj interface{}, methods string, m
}
}
// 执行对象方法注册methods参数不区分大小写
func (d *Domain) BindObjectMethod(pattern string, obj interface{}, method string) {
for domain, _ := range d.m {
d.s.BindObjectMethod(pattern+"@"+domain, obj, method)
@ -67,7 +64,6 @@ func (d *Domain) doBindObjectMethod(pattern string, obj interface{}, method stri
}
}
// RESTful执行对象注册
func (d *Domain) BindObjectRest(pattern string, obj interface{}) {
for domain, _ := range d.m {
d.s.BindObjectRest(pattern+"@"+domain, obj)
@ -80,7 +76,6 @@ func (d *Domain) doBindObjectRest(pattern string, obj interface{}, middleware []
}
}
// 控制器注册
func (d *Domain) BindController(pattern string, c Controller, methods ...string) {
for domain, _ := range d.m {
d.s.BindController(pattern+"@"+domain, c, methods...)
@ -93,7 +88,6 @@ func (d *Domain) doBindController(pattern string, c Controller, methods string,
}
}
// 控制器方法注册methods参数区分大小写
func (d *Domain) BindControllerMethod(pattern string, c Controller, method string) {
for domain, _ := range d.m {
d.s.BindControllerMethod(pattern+"@"+domain, c, method)
@ -106,7 +100,6 @@ func (d *Domain) doBindControllerMethod(pattern string, c Controller, method str
}
}
// RESTful控制器注册
func (d *Domain) BindControllerRest(pattern string, c Controller) {
for domain, _ := range d.m {
d.s.BindControllerRest(pattern+"@"+domain, c)
@ -119,43 +112,36 @@ func (d *Domain) doBindControllerRest(pattern string, c Controller, middleware [
}
}
// 绑定指定的hook回调函数, hook参数的值由ghttp server设定参数不区分大小写
// 目前hook支持Init/Shut
func (d *Domain) BindHookHandler(pattern string, hook string, handler HandlerFunc) {
for domain, _ := range d.m {
d.s.BindHookHandler(pattern+"@"+domain, hook, handler)
}
}
// 通过map批量绑定回调函数
func (d *Domain) BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) {
for domain, _ := range d.m {
d.s.BindHookHandlerByMap(pattern+"@"+domain, hookmap)
}
}
// 绑定指定的状态码回调函数
func (d *Domain) BindStatusHandler(status int, handler HandlerFunc) {
for domain, _ := range d.m {
d.s.setStatusHandler(d.s.statusHandlerKey(status, domain), handler)
}
}
// 通过map批量绑定状态码回调函数
func (d *Domain) BindStatusHandlerByMap(handlerMap map[int]HandlerFunc) {
for k, v := range handlerMap {
d.BindStatusHandler(k, v)
}
}
// 注册中间件,绑定到指定的路由规则上,中间件参数支持多个。
func (d *Domain) BindMiddleware(pattern string, handlers ...HandlerFunc) {
for domain, _ := range d.m {
d.s.BindMiddleware(pattern+"@"+domain, handlers...)
}
}
// 注册中间件,绑定到全局路由规则("/*")上,中间件参数支持多个。
func (d *Domain) BindMiddlewareDefault(handlers ...HandlerFunc) {
for domain, _ := range d.m {
d.s.BindMiddleware(gDEFAULT_MIDDLEWARE_PATTERN+"@"+domain, handlers...)

View File

@ -72,11 +72,13 @@ func (s *Server) setHandler(pattern string, handler *handlerItem) {
}
// 注册地址记录及重复注册判断
regKey := s.handlerKey(handler.hookName, method, uri, domain)
switch handler.itemType {
case gHANDLER_TYPE_HANDLER, gHANDLER_TYPE_OBJECT, gHANDLER_TYPE_CONTROLLER:
if item, ok := s.routesMap[regKey]; ok {
glog.Fatalf(`duplicated route registry "%s", already registered at %s`, pattern, item[0].file)
return
if !s.config.RouteOverWrite {
switch handler.itemType {
case gHANDLER_TYPE_HANDLER, gHANDLER_TYPE_OBJECT, gHANDLER_TYPE_CONTROLLER:
if item, ok := s.routesMap[regKey]; ok {
glog.Fatalf(`duplicated route registry "%s", already registered at %s`, pattern, item[0].file)
return
}
}
}
// 注册的路由信息对象
@ -263,7 +265,14 @@ func (s *Server) compareRouterPriority(newItem *handlerItem, oldItem *handlerIte
return true
}
// 最后新的规则比旧的规则优先级
// 如果是服务路由,那么新的规则比旧的规则优先级高(路由覆盖)
if newItem.itemType == gHANDLER_TYPE_HANDLER ||
newItem.itemType == gHANDLER_TYPE_OBJECT ||
newItem.itemType == gHANDLER_TYPE_CONTROLLER {
return true
}
// 如果是其他路由(HOOK/中间件),那么新的规则比旧的规则优先级低,使得注册相同路由则顺序执行
return false
}

View File

@ -67,6 +67,10 @@ func (s *Server) searchHandlers(method, path, domain string) (parsedItems []*han
// 多层链表(每个节点都有一个*list链表)的目的是当叶子节点未有任何规则匹配时,让父级模糊匹配规则继续处理
lists := make([]*glist.List, 0, 16)
for k, v := range array {
// In case of double '/' URI, eg: /user//index
if v == "" {
continue
}
if _, ok := p.(map[string]interface{})["*list"]; ok {
lists = append(lists, p.(map[string]interface{})["*list"].(*glist.List))
}

View File

@ -53,7 +53,7 @@ func (s *Server) doBindController(pattern string, controller Controller, method
// 当pattern中的method为all时去掉该method以便于后续方法判断
domain, method, path, err := s.parsePattern(pattern)
if err != nil {
glog.Error(err)
glog.Fatal(err)
return
}
if strings.EqualFold(method, gDEFAULT_METHOD) {
@ -63,15 +63,15 @@ func (s *Server) doBindController(pattern string, controller Controller, method
m := make(handlerMap)
v := reflect.ValueOf(controller)
t := v.Type()
sname := t.Elem().Name()
pkgPath := t.Elem().PkgPath()
pkgName := gfile.Basename(pkgPath)
structName := t.Elem().Name()
for i := 0; i < v.NumMethod(); i++ {
mname := t.Method(i).Name
if methodMap != nil && !methodMap[mname] {
methodName := t.Method(i).Name
if methodMap != nil && !methodMap[methodName] {
continue
}
if mname == "Init" || mname == "Shut" || mname == "Exit" {
if methodName == "Init" || methodName == "Shut" || methodName == "Exit" {
continue
}
ctlName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
@ -82,20 +82,20 @@ func (s *Server) doBindController(pattern string, controller Controller, method
if len(methodMap) > 0 {
// 指定的方法名称注册,那么需要使用错误提示
glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
pkgPath, ctlName, mname, v.Method(i).Type().String())
pkgPath, ctlName, methodName, v.Method(i).Type().String())
} else {
// 否则只是Debug提示
glog.Debugf(`ignore route method: %s.%s.%s defined as "%s", no match "func()"`,
pkgPath, ctlName, mname, v.Method(i).Type().String())
pkgPath, ctlName, methodName, v.Method(i).Type().String())
}
continue
}
key := s.mergeBuildInNameToPattern(pattern, sname, mname, true)
key := s.mergeBuildInNameToPattern(pattern, structName, methodName, true)
m[key] = &handlerItem{
itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, mname),
itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, methodName),
itemType: gHANDLER_TYPE_CONTROLLER,
ctrlInfo: &handlerController{
name: mname,
name: methodName,
reflect: v.Elem().Type(),
},
middleware: middleware,
@ -104,17 +104,17 @@ func (s *Server) doBindController(pattern string, controller Controller, method
// 例如: pattern为/user, 那么会同时注册/user及/user/index
// 这里处理新增/user路由绑定。
// 注意当pattern带有内置变量时不会自动加该路由。
if strings.EqualFold(mname, "Index") && !gregex.IsMatchString(`\{\.\w+\}`, pattern) {
if strings.EqualFold(methodName, "Index") && !gregex.IsMatchString(`\{\.\w+\}`, pattern) {
p := gstr.PosRI(key, "/index")
k := key[0:p] + key[p+6:]
if len(k) == 0 || k[0] == '@' {
k = "/" + k
}
m[k] = &handlerItem{
itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, mname),
itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, methodName),
itemType: gHANDLER_TYPE_CONTROLLER,
ctrlInfo: &handlerController{
name: mname,
name: methodName,
reflect: v.Elem().Type(),
},
middleware: middleware,
@ -128,11 +128,11 @@ func (s *Server) doBindControllerMethod(pattern string, controller Controller, m
m := make(handlerMap)
v := reflect.ValueOf(controller)
t := v.Type()
sname := t.Elem().Name()
mname := strings.TrimSpace(method)
fval := v.MethodByName(mname)
if !fval.IsValid() {
glog.Error("invalid method name:" + mname)
structName := t.Elem().Name()
methodName := strings.TrimSpace(method)
methodValue := v.MethodByName(methodName)
if !methodValue.IsValid() {
glog.Fatal("invalid method name: " + methodName)
return
}
pkgPath := t.Elem().PkgPath()
@ -141,17 +141,17 @@ func (s *Server) doBindControllerMethod(pattern string, controller Controller, m
if ctlName[0] == '*' {
ctlName = fmt.Sprintf(`(%s)`, ctlName)
}
if _, ok := fval.Interface().(func()); !ok {
if _, ok := methodValue.Interface().(func()); !ok {
glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
pkgPath, ctlName, mname, fval.Type().String())
pkgPath, ctlName, methodName, methodValue.Type().String())
return
}
key := s.mergeBuildInNameToPattern(pattern, sname, mname, false)
key := s.mergeBuildInNameToPattern(pattern, structName, methodName, false)
m[key] = &handlerItem{
itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, mname),
itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, methodName),
itemType: gHANDLER_TYPE_CONTROLLER,
ctrlInfo: &handlerController{
name: mname,
name: methodName,
reflect: v.Elem().Type(),
},
middleware: middleware,
@ -164,13 +164,12 @@ func (s *Server) doBindControllerRest(pattern string, controller Controller, mid
m := make(handlerMap)
v := reflect.ValueOf(controller)
t := v.Type()
sname := t.Elem().Name()
pkgPath := t.Elem().PkgPath()
structName := t.Elem().Name()
// 如果存在与HttpMethod对应名字的方法那么绑定这些方法
for i := 0; i < v.NumMethod(); i++ {
mname := t.Method(i).Name
method := strings.ToUpper(mname)
if _, ok := methodsMap[method]; !ok {
methodName := t.Method(i).Name
if _, ok := methodsMap[strings.ToUpper(methodName)]; !ok {
continue
}
pkgName := gfile.Basename(pkgPath)
@ -180,15 +179,15 @@ func (s *Server) doBindControllerRest(pattern string, controller Controller, mid
}
if _, ok := v.Method(i).Interface().(func()); !ok {
glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
pkgPath, ctlName, mname, v.Method(i).Type().String())
pkgPath, ctlName, methodName, v.Method(i).Type().String())
return
}
key := s.mergeBuildInNameToPattern(mname+":"+pattern, sname, mname, false)
key := s.mergeBuildInNameToPattern(methodName+":"+pattern, structName, methodName, false)
m[key] = &handlerItem{
itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, mname),
itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, methodName),
itemType: gHANDLER_TYPE_CONTROLLER,
ctrlInfo: &handlerController{
name: mname,
name: methodName,
reflect: v.Elem().Type(),
},
middleware: middleware,

View File

@ -51,19 +51,18 @@ func (s *Server) doBindObject(pattern string, object interface{}, method string,
// 当pattern中的method为all时去掉该method以便于后续方法判断
domain, method, path, err := s.parsePattern(pattern)
if err != nil {
glog.Error(err)
glog.Fatal(err)
return
}
if strings.EqualFold(method, gDEFAULT_METHOD) {
pattern = s.serveHandlerKey("", path, domain)
}
m := make(handlerMap)
v := reflect.ValueOf(object)
t := v.Type()
sname := t.Elem().Name()
initFunc := (func(*Request))(nil)
shutFunc := (func(*Request))(nil)
structName := t.Elem().Name()
if v.MethodByName("Init").IsValid() {
initFunc = v.MethodByName("Init").Interface().(func(*Request))
}
@ -73,11 +72,11 @@ func (s *Server) doBindObject(pattern string, object interface{}, method string,
pkgPath := t.Elem().PkgPath()
pkgName := gfile.Basename(pkgPath)
for i := 0; i < v.NumMethod(); i++ {
mname := t.Method(i).Name
if methodMap != nil && !methodMap[mname] {
methodName := t.Method(i).Name
if methodMap != nil && !methodMap[methodName] {
continue
}
if mname == "Init" || mname == "Shut" {
if methodName == "Init" || methodName == "Shut" {
continue
}
objName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
@ -89,17 +88,17 @@ func (s *Server) doBindObject(pattern string, object interface{}, method string,
if len(methodMap) > 0 {
// 指定的方法名称注册,那么需要使用错误提示
glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`,
pkgPath, objName, mname, v.Method(i).Type().String())
pkgPath, objName, methodName, v.Method(i).Type().String())
} else {
// 否则只是Debug提示
glog.Debugf(`ignore route method: %s.%s.%s defined as "%s", no match "func(*ghttp.Request)"`,
pkgPath, objName, mname, v.Method(i).Type().String())
pkgPath, objName, methodName, v.Method(i).Type().String())
}
continue
}
key := s.mergeBuildInNameToPattern(pattern, sname, mname, true)
key := s.mergeBuildInNameToPattern(pattern, structName, methodName, true)
m[key] = &handlerItem{
itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, mname),
itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName),
itemType: gHANDLER_TYPE_OBJECT,
itemFunc: itemFunc,
initFunc: initFunc,
@ -108,14 +107,14 @@ func (s *Server) doBindObject(pattern string, object interface{}, method string,
}
// 如果方法中带有Index方法那么额外自动增加一个路由规则匹配主URI。
// 注意当pattern带有内置变量时不会自动加该路由。
if strings.EqualFold(mname, "Index") && !gregex.IsMatchString(`\{\.\w+\}`, pattern) {
if strings.EqualFold(methodName, "Index") && !gregex.IsMatchString(`\{\.\w+\}`, pattern) {
p := gstr.PosRI(key, "/index")
k := key[0:p] + key[p+6:]
if len(k) == 0 || k[0] == '@' {
k = "/" + k
}
m[k] = &handlerItem{
itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, mname),
itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName),
itemType: gHANDLER_TYPE_OBJECT,
itemFunc: itemFunc,
initFunc: initFunc,
@ -133,11 +132,11 @@ func (s *Server) doBindObjectMethod(pattern string, object interface{}, method s
m := make(handlerMap)
v := reflect.ValueOf(object)
t := v.Type()
sname := t.Elem().Name()
mname := strings.TrimSpace(method)
fval := v.MethodByName(mname)
if !fval.IsValid() {
glog.Error("invalid method name:" + mname)
structName := t.Elem().Name()
methodName := strings.TrimSpace(method)
methodValue := v.MethodByName(methodName)
if !methodValue.IsValid() {
glog.Fatal("invalid method name: " + methodName)
return
}
initFunc := (func(*Request))(nil)
@ -154,15 +153,15 @@ func (s *Server) doBindObjectMethod(pattern string, object interface{}, method s
if objName[0] == '*' {
objName = fmt.Sprintf(`(%s)`, objName)
}
itemFunc, ok := fval.Interface().(func(*Request))
itemFunc, ok := methodValue.Interface().(func(*Request))
if !ok {
glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`,
pkgPath, objName, mname, fval.Type().String())
pkgPath, objName, methodName, methodValue.Type().String())
return
}
key := s.mergeBuildInNameToPattern(pattern, sname, mname, false)
key := s.mergeBuildInNameToPattern(pattern, structName, methodName, false)
m[key] = &handlerItem{
itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, mname),
itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName),
itemType: gHANDLER_TYPE_OBJECT,
itemFunc: itemFunc,
initFunc: initFunc,
@ -177,9 +176,9 @@ func (s *Server) doBindObjectRest(pattern string, object interface{}, middleware
m := make(handlerMap)
v := reflect.ValueOf(object)
t := v.Type()
sname := t.Elem().Name()
initFunc := (func(*Request))(nil)
shutFunc := (func(*Request))(nil)
structName := t.Elem().Name()
if v.MethodByName("Init").IsValid() {
initFunc = v.MethodByName("Init").Interface().(func(*Request))
}
@ -188,9 +187,8 @@ func (s *Server) doBindObjectRest(pattern string, object interface{}, middleware
}
pkgPath := t.Elem().PkgPath()
for i := 0; i < v.NumMethod(); i++ {
mname := t.Method(i).Name
method := strings.ToUpper(mname)
if _, ok := methodsMap[method]; !ok {
methodName := t.Method(i).Name
if _, ok := methodsMap[strings.ToUpper(methodName)]; !ok {
continue
}
pkgName := gfile.Basename(pkgPath)
@ -201,12 +199,12 @@ func (s *Server) doBindObjectRest(pattern string, object interface{}, middleware
itemFunc, ok := v.Method(i).Interface().(func(*Request))
if !ok {
glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`,
pkgPath, objName, mname, v.Method(i).Type().String())
pkgPath, objName, methodName, v.Method(i).Type().String())
continue
}
key := s.mergeBuildInNameToPattern(mname+":"+pattern, sname, mname, false)
key := s.mergeBuildInNameToPattern(methodName+":"+pattern, structName, methodName, false)
m[key] = &handlerItem{
itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, mname),
itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName),
itemType: gHANDLER_TYPE_OBJECT,
itemFunc: itemFunc,
initFunc: initFunc,

View File

@ -615,3 +615,58 @@ func Test_Middleware_CORSAndAuth(t *testing.T) {
gtest.Assert(client.GetContent("/api.v2/user/list", "token=123456"), "list")
})
}
func MiddlewareScope1(r *ghttp.Request) {
r.Response.Write("a")
r.Middleware.Next()
r.Response.Write("b")
}
func MiddlewareScope2(r *ghttp.Request) {
r.Response.Write("c")
r.Middleware.Next()
r.Response.Write("d")
}
func MiddlewareScope3(r *ghttp.Request) {
r.Response.Write("e")
r.Middleware.Next()
r.Response.Write("f")
}
func Test_Middleware_Scope(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareScope1)
group.ALL("/scope1", func(r *ghttp.Request) {
r.Response.Write("1")
})
group.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareScope2)
group.ALL("/scope2", func(r *ghttp.Request) {
r.Response.Write("2")
})
})
group.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareScope3)
group.ALL("/scope3", func(r *ghttp.Request) {
r.Response.Write("3")
})
})
})
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("/"), "Not Found")
gtest.Assert(client.GetContent("/scope1"), "a1b")
gtest.Assert(client.GetContent("/scope2"), "ac2db")
gtest.Assert(client.GetContent("/scope3"), "ae3fb")
})
}

View File

@ -153,7 +153,7 @@ func IntranetIPArray() (ips []string, err error) {
continue
}
ipStr := ip.String()
if ipStr != "127.0.0.1" && IsIntranet(ipStr) {
if IsIntranet(ipStr) {
ips = append(ips, ipStr)
}
}

View File

@ -19,7 +19,6 @@ import (
type Conn struct {
net.Conn // Underlying TCP connection object.
reader *bufio.Reader // Buffer reader for connection.
buffer []byte // Buffer object.
recvDeadline time.Time // Timeout point for reading.
sendDeadline time.Time // Timeout point for writing.
recvBufferWait time.Duration // Interval duration for reading buffer.
@ -87,7 +86,7 @@ func (c *Conn) Send(data []byte, retry ...Retry) error {
if retry[0].Interval == 0 {
retry[0].Interval = gDEFAULT_RETRY_INTERVAL
}
time.Sleep(time.Duration(retry[0].Interval) * time.Millisecond)
time.Sleep(retry[0].Interval)
}
} else {
return nil
@ -98,10 +97,11 @@ func (c *Conn) Send(data []byte, retry ...Retry) error {
// Recv receives data from the connection.
//
// Note that,
// 1. If length = 0, it means it receives the data from current buffer and returns immediately.
// 2. If length < 0, it means it receives all data from buffer and returns if it waits til no data from connection.
// 1. If length = 0, which means it receives the data from current buffer and returns immediately.
// 2. If length < 0, which means it receives all data from buffer and returns if it waits til no data from connection.
// Developers should notice the package parsing yourself if you decide receiving all data from buffer.
// 3. If length > 0, it means it blocks reading data from connection until length size was received.
// 3. If length > 0, which means it blocks reading data from connection until length size was received.
// It is the most commonly used length value for data receiving.
func (c *Conn) Recv(length int, retry ...Retry) ([]byte, error) {
var err error // Reading error.
var size int // Reading size.
@ -164,7 +164,7 @@ func (c *Conn) Recv(length int, retry ...Retry) ([]byte, error) {
if retry[0].Interval == 0 {
retry[0].Interval = gDEFAULT_RETRY_INTERVAL
}
time.Sleep(time.Duration(retry[0].Interval) * time.Millisecond)
time.Sleep(retry[0].Interval)
continue
}
break

View File

@ -13,34 +13,23 @@ import (
)
const (
gPKG_DEFAULT_MAX_DATA_SIZE = 65535 // (Byte) Max package size.
gPKG_DEFAULT_HEADER_SIZE = 2 // Header size for simple package protocol.
gPKG_MAX_HEADER_SIZE = 4 // Max header size for simple package protocol.
gPKG_HEADER_SIZE_DEFAULT = 2 // Header size for simple package protocol.
gPKG_HEADER_SIZE_MAX = 4 // Max header size for simple package protocol.
)
// Package option for simple protocol.
type PkgOption struct {
HeaderSize int // It's 2 bytes in default, 4 bytes max.
MaxDataSize int // (Byte) Data field size, it's 2 bytes in default, which means 65535 bytes.
Retry Retry // Retry policy.
}
// HeaderSize is used to mark the data length for next data receiving.
// It's 2 bytes in default, 4 bytes max, which stands for the max data length
// from 65535 to 4294967295 bytes.
HeaderSize int
// getPkgOption wraps and returns the PkgOption.
// If no option given, it returns a new option with default value.
func getPkgOption(option ...PkgOption) (*PkgOption, error) {
pkgOption := PkgOption{}
if len(option) > 0 {
pkgOption = option[0]
}
if pkgOption.HeaderSize == 0 {
pkgOption.HeaderSize = gPKG_DEFAULT_HEADER_SIZE
}
if pkgOption.MaxDataSize == 0 {
pkgOption.MaxDataSize = gPKG_DEFAULT_MAX_DATA_SIZE
} else if pkgOption.MaxDataSize > 0xFFFFFF {
return nil, fmt.Errorf(`package size %d exceeds allowed max size %d`, pkgOption.MaxDataSize, 0xFFFFFF)
}
return &pkgOption, nil
// MaxDataSize is the data field size in bytes for data length validation.
// If it's not manually set, it'll automatically be set correspondingly with the HeaderSize.
MaxDataSize int
// Retry policy when operation fails.
Retry Retry
}
// SendPkg send data using simple package protocol.
@ -48,7 +37,7 @@ func getPkgOption(option ...PkgOption) (*PkgOption, error) {
// Simple package protocol: DataLength(24bit)|DataField(variant)。
//
// Note that,
// 1. The DataLength is the length of DataField, which does not contain the header size 2 bytes.
// 1. The DataLength is the length of DataField, which does not contain the header size.
// 2. The integer bytes of the package are encoded using BigEndian order.
func (c *Conn) SendPkg(data []byte, option ...PkgOption) error {
pkgOption, err := getPkgOption(option...)
@ -57,16 +46,18 @@ func (c *Conn) SendPkg(data []byte, option ...PkgOption) error {
}
length := len(data)
if length > pkgOption.MaxDataSize {
return fmt.Errorf(`data size %d exceeds max pkg size %d`, length, pkgOption.MaxDataSize)
return fmt.Errorf(
`data too long, data size %d exceeds allowed max data size %d`,
length, pkgOption.MaxDataSize,
)
}
offset := gPKG_MAX_HEADER_SIZE - pkgOption.HeaderSize
buffer := make([]byte, gPKG_MAX_HEADER_SIZE+len(data))
offset := gPKG_HEADER_SIZE_MAX - pkgOption.HeaderSize
buffer := make([]byte, gPKG_HEADER_SIZE_MAX+len(data))
binary.BigEndian.PutUint32(buffer[0:], uint32(length))
copy(buffer[gPKG_MAX_HEADER_SIZE:], data)
copy(buffer[gPKG_HEADER_SIZE_MAX:], data)
if pkgOption.Retry.Count > 0 {
return c.Send(buffer[offset:], pkgOption.Retry)
}
//fmt.Println("SendPkg:", buffer[offset:])
return c.Send(buffer[offset:])
}
@ -100,56 +91,39 @@ func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option
// RecvPkg receives data from connection using simple package protocol.
func (c *Conn) RecvPkg(option ...PkgOption) (result []byte, err error) {
var temp []byte
var buffer []byte
var length int
pkgOption, err := getPkgOption(option...)
if err != nil {
return nil, err
}
for {
for {
if len(c.buffer) >= pkgOption.HeaderSize {
if length <= 0 {
switch pkgOption.HeaderSize {
case 1:
// It fills with zero if the header size is lesser than 4 bytes (uint32).
length = int(binary.BigEndian.Uint32([]byte{0, 0, 0, c.buffer[0]}))
case 2:
length = int(binary.BigEndian.Uint32([]byte{0, 0, c.buffer[0], c.buffer[1]}))
case 3:
length = int(binary.BigEndian.Uint32([]byte{0, c.buffer[0], c.buffer[1], c.buffer[2]}))
default:
length = int(binary.BigEndian.Uint32([]byte{c.buffer[0], c.buffer[1], c.buffer[2], c.buffer[3]}))
}
}
// It here validates the size of the package.
// It clears the buffer and returns error immediately if it validates failed.
if length < 0 || length > pkgOption.MaxDataSize {
c.buffer = c.buffer[:0]
return nil, fmt.Errorf(`invalid package size %d`, length)
}
// It continues reading until it receives complete bytes of the package.
if len(c.buffer) < length+pkgOption.HeaderSize {
break
}
result = c.buffer[pkgOption.HeaderSize : pkgOption.HeaderSize+length]
c.buffer = c.buffer[pkgOption.HeaderSize+length:]
length = 0
return
} else {
break
}
}
temp, err = c.Recv(0, pkgOption.Retry)
if err != nil {
break
}
if len(temp) > 0 {
c.buffer = append(c.buffer, temp...)
}
//fmt.Println("RecvPkg:", c.buffer)
// Header field.
buffer, err = c.Recv(pkgOption.HeaderSize, pkgOption.Retry)
if err != nil {
return nil, err
}
return
switch pkgOption.HeaderSize {
case 1:
// It fills with zero if the header size is lesser than 4 bytes (uint32).
length = int(binary.BigEndian.Uint32([]byte{0, 0, 0, buffer[0]}))
case 2:
length = int(binary.BigEndian.Uint32([]byte{0, 0, buffer[0], buffer[1]}))
case 3:
length = int(binary.BigEndian.Uint32([]byte{0, buffer[0], buffer[1], buffer[2]}))
default:
length = int(binary.BigEndian.Uint32([]byte{buffer[0], buffer[1], buffer[2], buffer[3]}))
}
// It here validates the size of the package.
// It clears the buffer and returns error immediately if it validates failed.
if length < 0 || length > pkgOption.MaxDataSize {
return nil, fmt.Errorf(`invalid package size %d`, length)
}
// Empty package.
if length == 0 {
return nil, nil
}
// Data field.
return c.Recv(length, pkgOption.Retry)
}
// RecvPkgWithTimeout reads data from connection with timeout using simple package protocol.
@ -161,3 +135,41 @@ func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, option ...PkgOption) (d
data, err = c.RecvPkg(option...)
return
}
// getPkgOption wraps and returns the PkgOption.
// If no option given, it returns a new option with default value.
func getPkgOption(option ...PkgOption) (*PkgOption, error) {
pkgOption := PkgOption{}
if len(option) > 0 {
pkgOption = option[0]
}
if pkgOption.HeaderSize == 0 {
pkgOption.HeaderSize = gPKG_HEADER_SIZE_DEFAULT
}
if pkgOption.HeaderSize > gPKG_HEADER_SIZE_MAX {
return nil, fmt.Errorf(
`package header size %d definition exceeds max header size %d`,
pkgOption.HeaderSize, gPKG_HEADER_SIZE_MAX,
)
}
if pkgOption.MaxDataSize == 0 {
switch pkgOption.HeaderSize {
case 1:
pkgOption.MaxDataSize = 0xFF
case 2:
pkgOption.MaxDataSize = 0xFFFF
case 3:
pkgOption.MaxDataSize = 0xFFFFFF
case 4:
// math.MaxInt32 not math.MaxUint32
pkgOption.MaxDataSize = 0x7FFFFFFF
}
}
if pkgOption.MaxDataSize > 0x7FFFFFFF {
return nil, fmt.Errorf(
`package data size %d definition exceeds allowed max data size %d`,
pkgOption.MaxDataSize, 0x7FFFFFFF,
)
}
return &pkgOption, nil
}

View File

@ -23,7 +23,7 @@ type PoolConn struct {
}
const (
gDEFAULT_POOL_EXPIRE = 30000 // (Millisecond) Default TTL for connection in the pool.
gDEFAULT_POOL_EXPIRE = 10000 // (Millisecond) Default TTL for connection in the pool.
gCONN_STATUS_UNKNOWN = 0 // Means it is unknown it's connective or not.
gCONN_STATUS_ACTIVE = 1 // Means it is now connective.
gCONN_STATUS_ERROR = 2 // Means it should be closed and removed from pool.
@ -36,27 +36,18 @@ var (
// NewPoolConn creates and returns a connection with pool feature.
func NewPoolConn(addr string, timeout ...time.Duration) (*PoolConn, error) {
var pool *gpool.Pool
if v := addressPoolMap.Get(addr); v == nil {
addressPoolMap.LockFunc(func(m map[string]interface{}) {
if v, ok := m[addr]; ok {
pool = v.(*gpool.Pool)
v := addressPoolMap.GetOrSetFuncLock(addr, func() interface{} {
var pool *gpool.Pool
pool = gpool.New(gDEFAULT_POOL_EXPIRE, func() (interface{}, error) {
if conn, err := NewConn(addr, timeout...); err == nil {
return &PoolConn{conn, pool, gCONN_STATUS_ACTIVE}, nil
} else {
pool = gpool.New(gDEFAULT_POOL_EXPIRE, func() (interface{}, error) {
if conn, err := NewConn(addr, timeout...); err == nil {
return &PoolConn{conn, pool, gCONN_STATUS_ACTIVE}, nil
} else {
return nil, err
}
})
m[addr] = pool
return nil, err
}
})
} else {
pool = v.(*gpool.Pool)
}
if v, err := pool.Get(); err == nil {
return pool
})
if v, err := v.(*gpool.Pool).Get(); err == nil {
return v.(*PoolConn), nil
} else {
return nil, err
@ -65,6 +56,9 @@ func NewPoolConn(addr string, timeout ...time.Duration) (*PoolConn, error) {
// Close puts back the connection to the pool if it's active,
// or closes the connection if it's not active.
//
// Note that, if <c> calls Close function closing itself, <c> can not
// be used again.
func (c *PoolConn) Close() error {
if c.pool != nil && c.status == gCONN_STATUS_ACTIVE {
c.status = gCONN_STATUS_UNKNOWN
@ -78,8 +72,8 @@ func (c *PoolConn) Close() error {
// Send writes data to the connection. It retrieves a new connection from its pool if it fails
// writing data.
func (c *PoolConn) Send(data []byte, retry ...Retry) error {
var err error
if err = c.Conn.Send(data, retry...); err != nil && c.status == gCONN_STATUS_UNKNOWN {
err := c.Conn.Send(data, retry...)
if err != nil && c.status == gCONN_STATUS_UNKNOWN {
if v, e := c.pool.Get(); e == nil {
c.Conn = v.(*PoolConn).Conn
err = c.Send(data, retry...)

View File

@ -10,6 +10,7 @@ import (
"crypto/tls"
"errors"
"net"
"sync"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/os/glog"
@ -23,10 +24,11 @@ const (
// TCP Server.
type Server struct {
listen net.Listener
address string
handler func(*Conn)
tlsConfig *tls.Config
mu sync.Mutex // Used for Server.listen concurrent safety.
listen net.Listener // Listener.
address string // Server listening address.
handler func(*Conn) // Connection handler.
tlsConfig *tls.Config // TLS configuration.
}
// Map for name to server, for singleton purpose.
@ -52,7 +54,7 @@ func NewServer(address string, handler func(*Conn), name ...string) *Server {
address: address,
handler: handler,
}
if len(name) > 0 {
if len(name) > 0 && name[0] != "" {
serverMapping.Set(name[0], s)
}
return s
@ -103,6 +105,11 @@ func (s *Server) SetTLSConfig(tlsConfig *tls.Config) {
// Close closes the listener and shutdowns the server.
func (s *Server) Close() error {
s.mu.Lock()
defer s.mu.Unlock()
if s.listen == nil {
return nil
}
return s.listen.Close()
}
@ -115,7 +122,9 @@ func (s *Server) Run() (err error) {
}
if s.tlsConfig != nil {
// TLS Server
s.mu.Lock()
s.listen, err = tls.Listen("tcp", s.address, s.tlsConfig)
s.mu.Unlock()
if err != nil {
glog.Error(err)
return
@ -127,15 +136,17 @@ func (s *Server) Run() (err error) {
glog.Error(err)
return err
}
s.mu.Lock()
s.listen, err = net.ListenTCP("tcp", addr)
s.mu.Unlock()
if err != nil {
glog.Error(err)
return err
}
}
// Listening loop.
for {
if conn, err := s.listen.Accept(); err != nil {
glog.Error(err)
return err
} else if conn != nil {
go s.handler(NewConnByNetConn(conn))

View File

@ -0,0 +1,21 @@
// Copyright 2017 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 gtcp_test
import (
"github.com/gogf/gf/container/garray"
)
var (
ports = garray.NewIntArray(true)
)
func init() {
for i := 9000; i <= 10000; i++ {
ports.Append(i)
}
}

View File

@ -0,0 +1,172 @@
// Copyright 2017 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 gtcp_test
import (
"fmt"
"github.com/gogf/gf/net/gtcp"
"github.com/gogf/gf/test/gtest"
"github.com/gogf/gf/util/gconv"
"testing"
"time"
)
func Test_Package_Basic(t *testing.T) {
p := ports.PopRand()
s := gtcp.NewServer(fmt.Sprintf(`:%d`, p), func(conn *gtcp.Conn) {
defer conn.Close()
for {
data, err := conn.RecvPkg()
if err != nil {
break
}
conn.SendPkg(data)
}
})
go s.Run()
defer s.Close()
time.Sleep(100 * time.Millisecond)
// SendPkg
gtest.Case(t, func() {
conn, err := gtcp.NewConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
defer conn.Close()
for i := 0; i < 100; i++ {
err := conn.SendPkg([]byte(gconv.String(i)))
gtest.Assert(err, nil)
}
for i := 0; i < 100; i++ {
err := conn.SendPkgWithTimeout([]byte(gconv.String(i)), time.Second)
gtest.Assert(err, nil)
}
})
// SendPkg with big data - failure.
gtest.Case(t, func() {
conn, err := gtcp.NewConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
defer conn.Close()
data := make([]byte, 65536)
err = conn.SendPkg(data)
gtest.AssertNE(err, nil)
})
// SendRecvPkg
gtest.Case(t, func() {
conn, err := gtcp.NewConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
defer conn.Close()
for i := 100; i < 200; i++ {
data := []byte(gconv.String(i))
result, err := conn.SendRecvPkg(data)
gtest.Assert(err, nil)
gtest.Assert(result, data)
}
for i := 100; i < 200; i++ {
data := []byte(gconv.String(i))
result, err := conn.SendRecvPkgWithTimeout(data, time.Second)
gtest.Assert(err, nil)
gtest.Assert(result, data)
}
})
// SendRecvPkg with big data - failure.
gtest.Case(t, func() {
conn, err := gtcp.NewConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
defer conn.Close()
data := make([]byte, 65536)
result, err := conn.SendRecvPkg(data)
gtest.AssertNE(err, nil)
gtest.Assert(result, nil)
})
// SendRecvPkg with big data - success.
gtest.Case(t, func() {
conn, err := gtcp.NewConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
defer conn.Close()
data := make([]byte, 65500)
data[100] = byte(65)
data[65400] = byte(85)
result, err := conn.SendRecvPkg(data)
gtest.Assert(err, nil)
gtest.Assert(result, data)
})
}
func Test_Package_Timeout(t *testing.T) {
p := ports.PopRand()
s := gtcp.NewServer(fmt.Sprintf(`:%d`, p), func(conn *gtcp.Conn) {
defer conn.Close()
for {
data, err := conn.RecvPkg()
if err != nil {
break
}
time.Sleep(time.Second)
gtest.Assert(conn.SendPkg(data), nil)
}
})
go s.Run()
defer s.Close()
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
conn, err := gtcp.NewConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
defer conn.Close()
data := []byte("10000")
result, err := conn.SendRecvPkgWithTimeout(data, time.Millisecond*500)
gtest.AssertNE(err, nil)
gtest.Assert(result, nil)
})
gtest.Case(t, func() {
conn, err := gtcp.NewConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
defer conn.Close()
data := []byte("10000")
result, err := conn.SendRecvPkgWithTimeout(data, time.Second*2)
gtest.Assert(err, nil)
gtest.Assert(result, data)
})
}
func Test_Package_Option(t *testing.T) {
p := ports.PopRand()
s := gtcp.NewServer(fmt.Sprintf(`:%d`, p), func(conn *gtcp.Conn) {
defer conn.Close()
option := gtcp.PkgOption{HeaderSize: 1}
for {
data, err := conn.RecvPkg(option)
if err != nil {
break
}
gtest.Assert(conn.SendPkg(data, option), nil)
}
})
go s.Run()
defer s.Close()
time.Sleep(100 * time.Millisecond)
// SendRecvPkg with big data - failure.
gtest.Case(t, func() {
conn, err := gtcp.NewConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
defer conn.Close()
data := make([]byte, 0xFF+1)
result, err := conn.SendRecvPkg(data, gtcp.PkgOption{HeaderSize: 1})
gtest.AssertNE(err, nil)
gtest.Assert(result, nil)
})
// SendRecvPkg with big data - success.
gtest.Case(t, func() {
conn, err := gtcp.NewConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
defer conn.Close()
data := make([]byte, 0xFF)
data[100] = byte(65)
data[200] = byte(85)
result, err := conn.SendRecvPkg(data, gtcp.PkgOption{HeaderSize: 1})
gtest.Assert(err, nil)
gtest.Assert(result, data)
})
}

View File

@ -0,0 +1,172 @@
// Copyright 2017 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 gtcp_test
import (
"fmt"
"github.com/gogf/gf/net/gtcp"
"github.com/gogf/gf/test/gtest"
"github.com/gogf/gf/util/gconv"
"testing"
"time"
)
func Test_Pool_Package_Basic(t *testing.T) {
p := ports.PopRand()
s := gtcp.NewServer(fmt.Sprintf(`:%d`, p), func(conn *gtcp.Conn) {
defer conn.Close()
for {
data, err := conn.RecvPkg()
if err != nil {
break
}
conn.SendPkg(data)
}
})
go s.Run()
defer s.Close()
time.Sleep(100 * time.Millisecond)
// SendPkg
gtest.Case(t, func() {
conn, err := gtcp.NewPoolConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
defer conn.Close()
for i := 0; i < 100; i++ {
err := conn.SendPkg([]byte(gconv.String(i)))
gtest.Assert(err, nil)
}
for i := 0; i < 100; i++ {
err := conn.SendPkgWithTimeout([]byte(gconv.String(i)), time.Second)
gtest.Assert(err, nil)
}
})
// SendPkg with big data - failure.
gtest.Case(t, func() {
conn, err := gtcp.NewPoolConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
defer conn.Close()
data := make([]byte, 65536)
err = conn.SendPkg(data)
gtest.AssertNE(err, nil)
})
// SendRecvPkg
gtest.Case(t, func() {
conn, err := gtcp.NewPoolConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
defer conn.Close()
for i := 100; i < 200; i++ {
data := []byte(gconv.String(i))
result, err := conn.SendRecvPkg(data)
gtest.Assert(err, nil)
gtest.Assert(result, data)
}
for i := 100; i < 200; i++ {
data := []byte(gconv.String(i))
result, err := conn.SendRecvPkgWithTimeout(data, time.Second)
gtest.Assert(err, nil)
gtest.Assert(result, data)
}
})
// SendRecvPkg with big data - failure.
gtest.Case(t, func() {
conn, err := gtcp.NewPoolConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
defer conn.Close()
data := make([]byte, 65536)
result, err := conn.SendRecvPkg(data)
gtest.AssertNE(err, nil)
gtest.Assert(result, nil)
})
// SendRecvPkg with big data - success.
gtest.Case(t, func() {
conn, err := gtcp.NewPoolConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
defer conn.Close()
data := make([]byte, 65500)
data[100] = byte(65)
data[65400] = byte(85)
result, err := conn.SendRecvPkg(data)
gtest.Assert(err, nil)
gtest.Assert(result, data)
})
}
func Test_Pool_Package_Timeout(t *testing.T) {
p := ports.PopRand()
s := gtcp.NewServer(fmt.Sprintf(`:%d`, p), func(conn *gtcp.Conn) {
defer conn.Close()
for {
data, err := conn.RecvPkg()
if err != nil {
break
}
time.Sleep(time.Second)
gtest.Assert(conn.SendPkg(data), nil)
}
})
go s.Run()
defer s.Close()
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
conn, err := gtcp.NewPoolConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
defer conn.Close()
data := []byte("10000")
result, err := conn.SendRecvPkgWithTimeout(data, time.Millisecond*500)
gtest.AssertNE(err, nil)
gtest.Assert(result, nil)
})
gtest.Case(t, func() {
conn, err := gtcp.NewPoolConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
defer conn.Close()
data := []byte("10000")
result, err := conn.SendRecvPkgWithTimeout(data, time.Second*2)
gtest.Assert(err, nil)
gtest.Assert(result, data)
})
}
func Test_Pool_Package_Option(t *testing.T) {
p := ports.PopRand()
s := gtcp.NewServer(fmt.Sprintf(`:%d`, p), func(conn *gtcp.Conn) {
defer conn.Close()
option := gtcp.PkgOption{HeaderSize: 1}
for {
data, err := conn.RecvPkg(option)
if err != nil {
break
}
gtest.Assert(conn.SendPkg(data, option), nil)
}
})
go s.Run()
defer s.Close()
time.Sleep(100 * time.Millisecond)
// SendRecvPkg with big data - failure.
gtest.Case(t, func() {
conn, err := gtcp.NewPoolConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
defer conn.Close()
data := make([]byte, 0xFF+1)
result, err := conn.SendRecvPkg(data, gtcp.PkgOption{HeaderSize: 1})
gtest.AssertNE(err, nil)
gtest.Assert(result, nil)
})
// SendRecvPkg with big data - success.
gtest.Case(t, func() {
conn, err := gtcp.NewPoolConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
defer conn.Close()
data := make([]byte, 0xFF)
data[100] = byte(65)
data[200] = byte(85)
result, err := conn.SendRecvPkg(data, gtcp.PkgOption{HeaderSize: 1})
gtest.Assert(err, nil)
gtest.Assert(result, data)
})
}

View File

@ -0,0 +1,65 @@
// Copyright 2017 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 gtcp_test
import (
"fmt"
"github.com/gogf/gf/net/gtcp"
"github.com/gogf/gf/test/gtest"
"testing"
"time"
)
func Test_Pool_Basic1(t *testing.T) {
p := ports.PopRand()
s := gtcp.NewServer(fmt.Sprintf(`:%d`, p), func(conn *gtcp.Conn) {
defer conn.Close()
for {
data, err := conn.RecvPkg()
if err != nil {
break
}
conn.SendPkg(data)
}
})
go s.Run()
defer s.Close()
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
conn, err := gtcp.NewPoolConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
defer conn.Close()
data := []byte("9999")
err = conn.SendPkg(data)
gtest.Assert(err, nil)
err = conn.SendPkgWithTimeout(data, time.Second)
gtest.Assert(err, nil)
})
}
func Test_Pool_Basic2(t *testing.T) {
p := ports.PopRand()
s := gtcp.NewServer(fmt.Sprintf(`:%d`, p), func(conn *gtcp.Conn) {
conn.Close()
})
go s.Run()
defer s.Close()
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
conn, err := gtcp.NewPoolConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
defer conn.Close()
data := []byte("9999")
err = conn.SendPkg(data)
gtest.Assert(err, nil)
//err = conn.SendPkgWithTimeout(data, time.Second)
//gtest.Assert(err, nil)
_, err = conn.SendRecv(data, -1)
gtest.AssertNE(err, nil)
})
}

View File

@ -154,7 +154,7 @@ func (c *Conn) Recv(length int, retry ...Retry) ([]byte, error) {
if retry[0].Interval == 0 {
retry[0].Interval = gDEFAULT_RETRY_INTERVAL
}
time.Sleep(time.Duration(retry[0].Interval) * time.Millisecond)
time.Sleep(retry[0].Interval)
continue
}
break

View File

@ -53,7 +53,7 @@ func NewServer(address string, handler func(*Conn), name ...string) *Server {
address: address,
handler: handler,
}
if len(name) > 0 {
if len(name) > 0 && name[0] != "" {
serverMapping.Set(name[0], s)
}
return s

72
os/gbuild/gbuild.go Normal file
View File

@ -0,0 +1,72 @@
// 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 gbuild manages the build-in variables from "gf build".
package gbuild
import (
"encoding/json"
"github.com/gogf/gf"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/encoding/gbase64"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/util/gconv"
"runtime"
)
var (
builtInVarStr = "" // Raw variable base64 string.
builtInVarMap = map[string]interface{}{} // Binary custom variable map decoded.
)
func init() {
if builtInVarStr != "" {
err := json.Unmarshal(gbase64.MustDecodeString(builtInVarStr), &builtInVarMap)
if err != nil {
intlog.Error(err)
}
builtInVarMap["gfVersion"] = gf.VERSION
builtInVarMap["goVersion"] = runtime.Version()
}
}
// Info returns the basic built information of the binary as map.
// Note that it should be used with gf-cli tool "gf build",
// which injects necessary information into the binary.
func Info() map[string]string {
return map[string]string{
"gf": GetString("gfVersion"),
"go": GetString("goVersion"),
"git": GetString("builtGit"),
"time": GetString("builtTime"),
}
}
// Get retrieves and returns the build-in binary variable with given name.
func Get(name string, def ...interface{}) interface{} {
if v, ok := builtInVarMap[name]; ok {
return v
}
if len(def) > 0 {
return def[0]
}
return nil
}
// Get retrieves and returns the build-in binary variable of given name as *gvar.Var.
func GetVar(name string, def ...interface{}) *gvar.Var {
return gvar.New(Get(name, def...))
}
// GetString retrieves and returns the build-in binary variable of given name as string.
func GetString(name string, def ...interface{}) string {
return gconv.String(Get(name, def...))
}
// Map returns the custom build-in variable map.
func Map() map[string]interface{} {
return builtInVarMap
}

View File

@ -13,29 +13,20 @@ import "time"
var cache = New()
// Set sets cache with <key>-<value> pair, which is expired after <duration>.
//
// The parameter <duration> can be either type of int or time.Duration.
// If <duration> is type of int, it means <duration> milliseconds.
// If <duration> <=0 means it does not expire.
// It does not expire if <duration> <= 0.
func Set(key interface{}, value interface{}, duration time.Duration) {
cache.Set(key, value, duration)
}
// SetIfNotExist sets cache with <key>-<value> pair if <key> does not exist in the cache,
// which is expired after <duration>.
//
// The parameter <duration> can be either type of int or time.Duration.
// If <duration> is type of int, it means <duration> milliseconds.
// If <duration> <=0 means it does not expire.
// which is expired after <duration>. It does not expire if <duration> <= 0.
func SetIfNotExist(key interface{}, value interface{}, duration time.Duration) bool {
return cache.SetIfNotExist(key, value, duration)
}
// Sets batch sets cache with key-value pairs by <data>, which is expired after <duration>.
//
// The parameter <duration> can be either type of int or time.Duration.
// If <duration> is type of int, it means <duration> milliseconds.
// If <duration> <=0 means it does not expire.
// It does not expire if <duration> <= 0.
func Sets(data map[interface{}]interface{}, duration time.Duration) {
cache.Sets(data, duration)
}
@ -50,33 +41,21 @@ func Get(key interface{}) interface{} {
// or sets <key>-<value> pair and returns <value> if <key> does not exist in the cache.
// The key-value pair expires after <duration>.
//
// The parameter <duration> can be either type of int or time.Duration.
// If <duration> is type of int, it means <duration> milliseconds.
// If <duration> <=0 means it does not expire.
// It does not expire if <duration> <= 0.
func GetOrSet(key interface{}, value interface{}, duration time.Duration) interface{} {
return cache.GetOrSet(key, value, duration)
}
// GetOrSetFunc returns the value of <key>,
// or sets <key> with result of function <f> and returns its result
// if <key> does not exist in the cache.
// The key-value pair expires after <duration>.
//
// The parameter <duration> can be either type of int or time.Duration.
// If <duration> is type of int, it means <duration> milliseconds.
// If <duration> <=0 means it does not expire.
// GetOrSetFunc returns the value of <key>, or sets <key> with result of function <f>
// and returns its result if <key> does not exist in the cache. The key-value pair expires
// after <duration>. It does not expire if <duration> <= 0.
func GetOrSetFunc(key interface{}, f func() interface{}, duration time.Duration) interface{} {
return cache.GetOrSetFunc(key, f, duration)
}
// GetOrSetFuncLock returns the value of <key>,
// or sets <key> with result of function <f> and returns its result
// if <key> does not exist in the cache.
// The key-value pair expires after <duration>.
//
// The parameter <duration> can be either type of int or time.Duration.
// If <duration> is type of int, it means <duration> milliseconds.
// If <duration> <=0 means it does not expire.
// GetOrSetFuncLock returns the value of <key>, or sets <key> with result of function <f>
// and returns its result if <key> does not exist in the cache. The key-value pair expires
// after <duration>. It does not expire if <duration> <= 0.
//
// Note that the function <f> is executed within writing mutex lock.
func GetOrSetFuncLock(key interface{}, f func() interface{}, duration time.Duration) interface{} {

View File

@ -106,9 +106,7 @@ func (c *memCache) getOrNewExpireSet(expire int64) (expireSet *gset.Set) {
// Set sets cache with <key>-<value> pair, which is expired after <duration>.
//
// The parameter <duration> can be either type of int or time.Duration.
// If <duration> is type of int, it means <duration> milliseconds.
// If <duration> <=0 means it does not expire.
// It does not expire if <duration> <= 0.
func (c *memCache) Set(key interface{}, value interface{}, duration time.Duration) {
expireTime := c.getInternalExpire(duration.Nanoseconds() / 1000000)
c.dataMu.Lock()
@ -120,9 +118,7 @@ func (c *memCache) Set(key interface{}, value interface{}, duration time.Duratio
// doSetWithLockCheck sets cache with <key>-<value> pair if <key> does not exist in the cache,
// which is expired after <duration>.
//
// The parameter <duration> can be either type of int or time.Duration.
// If <duration> is type of int, it means <duration> milliseconds.
// If <duration> <=0 means it does not expire.
// It does not expire if <duration> <= 0.
//
// It doubly checks the <key> whether exists in the cache using mutex writing lock
// before setting it to the cache.
@ -154,11 +150,7 @@ func (c *memCache) getInternalExpire(expire int64) int64 {
}
// SetIfNotExist sets cache with <key>-<value> pair if <key> does not exist in the cache,
// which is expired after <duration>.
//
// The parameter <duration> can be either type of int or time.Duration.
// If <duration> is type of int, it means <duration> milliseconds.
// If <duration> <=0 means it does not expire.
// which is expired after <duration>. It does not expire if <duration> <= 0.
func (c *memCache) SetIfNotExist(key interface{}, value interface{}, duration time.Duration) bool {
if !c.Contains(key) {
c.doSetWithLockCheck(key, value, duration)
@ -169,9 +161,7 @@ func (c *memCache) SetIfNotExist(key interface{}, value interface{}, duration ti
// Sets batch sets cache with key-value pairs by <data>, which is expired after <duration>.
//
// The parameter <duration> can be either type of int or time.Duration.
// If <duration> is type of int, it means <duration> milliseconds.
// If <duration> <=0 means it does not expire.
// It does not expire if <duration> <= 0.
func (c *memCache) Sets(data map[interface{}]interface{}, duration time.Duration) {
expireTime := c.getInternalExpire(duration.Nanoseconds() / 1000000)
for k, v := range data {
@ -198,13 +188,9 @@ func (c *memCache) Get(key interface{}) interface{} {
return nil
}
// GetOrSet returns the value of <key>,
// or sets <key>-<value> pair and returns <value> if <key> does not exist in the cache.
// The key-value pair expires after <duration>.
//
// The parameter <duration> can be either type of int or time.Duration.
// If <duration> is type of int, it means <duration> milliseconds.
// If <duration> <=0 means it does not expire.
// GetOrSet returns the value of <key>, or sets <key>-<value> pair and returns <value> if <key>
// does not exist in the cache. The key-value pair expires after <duration>. It does not expire
// if <duration> <= 0.
func (c *memCache) GetOrSet(key interface{}, value interface{}, duration time.Duration) interface{} {
if v := c.Get(key); v == nil {
return c.doSetWithLockCheck(key, value, duration)
@ -213,14 +199,9 @@ func (c *memCache) GetOrSet(key interface{}, value interface{}, duration time.Du
}
}
// GetOrSetFunc returns the value of <key>,
// or sets <key> with result of function <f> and returns its result
// if <key> does not exist in the cache.
// The key-value pair expires after <duration>.
//
// The parameter <duration> can be either type of int or time.Duration.
// If <duration> is type of int, it means <duration> milliseconds.
// If <duration> <=0 means it does not expire.
// GetOrSetFunc returns the value of <key>, or sets <key> with result of function <f>
// and returns its result if <key> does not exist in the cache. The key-value pair expires
// after <duration>. It does not expire if <duration> <= 0.
func (c *memCache) GetOrSetFunc(key interface{}, f func() interface{}, duration time.Duration) interface{} {
if v := c.Get(key); v == nil {
return c.doSetWithLockCheck(key, f(), duration)
@ -229,14 +210,9 @@ func (c *memCache) GetOrSetFunc(key interface{}, f func() interface{}, duration
}
}
// GetOrSetFuncLock returns the value of <key>,
// or sets <key> with result of function <f> and returns its result
// if <key> does not exist in the cache.
// The key-value pair expires after <duration>.
//
// The parameter <duration> can be either type of int or time.Duration.
// If <duration> is type of int, it means <duration> milliseconds.
// If <duration> <=0 means it does not expire.
// GetOrSetFuncLock returns the value of <key>, or sets <key> with result of function <f>
// and returns its result if <key> does not exist in the cache. The key-value pair expires
// after <duration>. It does not expire if <duration> <= 0.
//
// Note that the function <f> is executed within writing mutex lock.
func (c *memCache) GetOrSetFuncLock(key interface{}, f func() interface{}, duration time.Duration) interface{} {

View File

@ -44,6 +44,13 @@ func (c *Config) GetMap(pattern string, def ...interface{}) map[string]interface
return nil
}
func (c *Config) GetMapStrStr(pattern string, def ...interface{}) map[string]string {
if j := c.getJson(); j != nil {
return j.GetMapStrStr(pattern, def...)
}
return nil
}
func (c *Config) GetArray(pattern string, def ...interface{}) []interface{} {
if j := c.getJson(); j != nil {
return j.GetArray(pattern, def...)

View File

@ -13,7 +13,7 @@ import "fmt"
func Scan(info ...interface{}) string {
var s string
fmt.Print(info...)
fmt.Scan(&s)
fmt.Scanln(&s)
return s
}
@ -21,6 +21,6 @@ func Scan(info ...interface{}) string {
func Scanf(format string, info ...interface{}) string {
var s string
fmt.Printf(format, info...)
fmt.Scan(&s)
fmt.Scanln(&s)
return s
}

View File

@ -36,8 +36,16 @@ var (
// The absolute file path for main package.
// It can be only checked and set once.
mainPkgPath = gtype.NewString()
// Temporary directory of system.
tempDir = "/tmp"
)
func init() {
if !Exists(tempDir) {
tempDir = os.TempDir()
}
}
// Mkdir creates directories recursively with given <path>.
// The parameter <path> is suggested to be absolute path.
func Mkdir(path string) error {
@ -511,5 +519,5 @@ func homeWindows() (string, error) {
// See os.TempDir().
func TempDir() string {
return os.TempDir()
return tempDir
}

View File

@ -9,10 +9,10 @@ package gproc
import (
"bytes"
"github.com/gogf/gf/text/gstr"
"io"
"os"
"runtime"
"strings"
"time"
"github.com/gogf/gf/os/gfile"
@ -21,25 +21,25 @@ import (
const (
gPROC_ENV_KEY_PPID_KEY = "GPROC_PPID"
gPROC_TEMP_DIR_ENV_KEY = "GPROC_TEMP_DIR"
)
var (
// 进程开始执行时间
// processPid is the pid of current process.
processPid = os.Getpid()
// processStartTime is the start time of current process.
processStartTime = time.Now()
)
// 获取当前进程ID
// Pid returns the pid of current process.
func Pid() int {
return os.Getpid()
return processPid
}
// 获取父进程ID(gproc父进程如果当前进程本身就是父进程那么返回自身的pid不存在时则使用系统父进程)
// PPid returns the custom parent pid if exists, or else it returns the system parent pid.
func PPid() int {
if !IsChild() {
return Pid()
}
// gPROC_ENV_KEY_PPID_KEY为gproc包自定义的父进程
ppidValue := os.Getenv(gPROC_ENV_KEY_PPID_KEY)
if ppidValue != "" && ppidValue != "0" {
return gconv.Int(ppidValue)
@ -47,18 +47,22 @@ func PPid() int {
return PPidOS()
}
// 获取父进程ID(系统父进程)
// PPidOS returns the system parent pid of current process.
// Note that the difference between PPidOS and PPid function is that the PPidOS returns
// the system ppid, but the PPid functions may return the custom pid by gproc if the custom
// ppid exists.
func PPidOS() int {
return os.Getppid()
}
// 判断当前进程是否为gproc创建的子进程
// IsChild checks and returns whether current process is a child process.
// A child process is forked by another gproc process.
func IsChild() bool {
ppidValue := os.Getenv(gPROC_ENV_KEY_PPID_KEY)
return ppidValue != "" && ppidValue != "0"
}
// 设置gproc父进程ID当ppid为0时表示该进程为gproc主进程否则为gproc子进程
// SetPPid sets custom parent pid for current process.
func SetPPid(ppid int) error {
if ppid > 0 {
return os.Setenv(gPROC_ENV_KEY_PPID_KEY, gconv.String(ppid))
@ -67,64 +71,99 @@ func SetPPid(ppid int) error {
}
}
// 进程开始执行时间
// StartTime returns the start time of current process.
func StartTime() time.Time {
return processStartTime
}
// 进程已经运行的时间(毫秒)
func Uptime() int {
return int(time.Now().UnixNano()/1e6 - processStartTime.UnixNano()/1e6)
// Uptime returns the duration which current process has been running
func Uptime() time.Duration {
return time.Now().Sub(processStartTime)
}
// 阻塞执行shell指令并给定输入输出对象
// Shell executes command <cmd> synchronizingly with given input pipe <in> and output pipe <out>.
// The command <cmd> reads the input parameters from input pipe <in>, and writes its output automatically
// to output pipe <out>.
func Shell(cmd string, out io.Writer, in io.Reader) error {
p := NewProcess(getShell(), []string{getShellOption(), cmd})
p := NewProcess(getShell(), append([]string{getShellOption()}, parseCommand(cmd)...))
p.Stdin = in
p.Stdout = out
return p.Run()
}
// 阻塞执行shell指令并输出结果当终端(如果需要异步请使用goroutine)
// ShellRun executes given command <cmd> synchronizingly and outputs the command result to the stdout.
func ShellRun(cmd string) error {
p := NewProcess(getShell(), []string{getShellOption(), cmd})
p := NewProcess(getShell(), append([]string{getShellOption()}, parseCommand(cmd)...))
return p.Run()
}
// 阻塞执行shell指令并返回输出结果(如果需要异步请使用goroutine)
// ShellExec executes given command <cmd> synchronizingly and returns the command result.
func ShellExec(cmd string, environment ...[]string) (string, error) {
buf := bytes.NewBuffer(nil)
p := NewProcess(getShell(), []string{getShellOption(), cmd}, environment...)
p := NewProcess(getShell(), append([]string{getShellOption()}, parseCommand(cmd)...), environment...)
p.Stdout = buf
err := p.Run()
return buf.String(), err
}
// 检测环境变量中是否已经存在指定键名
func checkEnvKey(env []string, key string) bool {
for _, v := range env {
if len(v) >= len(key) && strings.EqualFold(v[0:len(key)], key) {
return true
// parseCommand parses command <cmd> into slice arguments.
//
// Note that it just parses the <cmd> for "cmd.exe" binary in windows, but it is not necessary
// parsing the <cmd> for other systems using "bash"/"sh" binary.
func parseCommand(cmd string) (args []string) {
if runtime.GOOS != "windows" {
return []string{cmd}
}
// Just for "cmd.exe" in windows.
var argStr string
var firstChar, prevChar, lastChar1, lastChar2 byte
array := gstr.SplitAndTrim(cmd, " ")
for _, v := range array {
if len(argStr) > 0 {
argStr += " "
}
firstChar = v[0]
lastChar1 = v[len(v)-1]
lastChar2 = 0
if len(v) > 1 {
lastChar2 = v[len(v)-2]
}
if prevChar == 0 && (firstChar == '"' || firstChar == '\'') {
// It should remove the first quote char.
argStr += v[1:]
prevChar = firstChar
} else if prevChar != 0 && lastChar2 != '\\' && lastChar1 == prevChar {
// It should remove the last quote char.
argStr += v[:len(v)-1]
args = append(args, argStr)
argStr = ""
prevChar = 0
} else if len(argStr) > 0 {
argStr += v
} else {
args = append(args, v)
}
}
return false
return
}
// 获取当前系统下的shell路径
// getShell returns the shell command depending on current working operation system.
// It returns "cmd.exe" for windows, and "bash" or "sh" for others.
func getShell() string {
switch runtime.GOOS {
case "windows":
return searchBinFromEnvPath("cmd.exe")
return SearchBinary("cmd.exe")
default:
path := searchBinFromEnvPath("bash")
path := SearchBinary("bash")
if path == "" {
path = searchBinFromEnvPath("sh")
path = SearchBinary("sh")
}
return path
}
}
// 获取当前系统默认shell执行指令的option参数
// getShellOption returns the shell option depending on current working operation system.
// It returns "/c" for windows, and "-c" for others.
func getShellOption() string {
switch runtime.GOOS {
case "windows":
@ -134,25 +173,26 @@ func getShellOption() string {
}
}
// 从环境变量PATH中搜索可执行文件
func searchBinFromEnvPath(file string) string {
// 如果是绝对路径,或者相对路径下存在,那么直接返回
// SearchBinary searches the binary <file> in current working folder and PATH environment.
func SearchBinary(file string) string {
// Check if it's absolute path of exists at current working directory.
if gfile.Exists(file) {
return file
}
array := ([]string)(nil)
switch runtime.GOOS {
case "windows":
array = strings.Split(os.Getenv("Path"), ";")
array = gstr.SplitAndTrim(os.Getenv("Path"), ";")
if gfile.Ext(file) != ".exe" {
file += ".exe"
}
default:
array = strings.Split(os.Getenv("PATH"), ":")
array = gstr.SplitAndTrim(os.Getenv("PATH"), ":")
}
if len(array) > 0 {
path := ""
for _, v := range array {
path := v + gfile.Separator + file
path = v + gfile.Separator + file
if gfile.Exists(path) {
return path
}

View File

@ -3,42 +3,81 @@
// 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 gproc
import (
"os"
"errors"
"fmt"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/net/gtcp"
"github.com/gogf/gf/os/gfcache"
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/util/gconv"
)
// 进程通信数据结构
type Msg struct {
SendPid int `json:"spid"` // 发送进程ID
RecvPid int `json:"rpid"` // 接收进程ID
Group string `json:"group"` // 分组名称
Data []byte `json:"data"` // 原始数据
// MsgRequest is the request structure for process communication.
type MsgRequest struct {
SendPid int // Sender PID.
RecvPid int // Receiver PID.
Group string // Message group name.
Data []byte // Request data.
}
// 本地进程通信接收消息队列(按照分组进行构建的map键值为*gqueue.Queue对象)
var commReceiveQueues = gmap.NewStrAnyMap(true)
// (用于发送)已建立的PID对应的Conn通信对象键值为一个Pool防止并行使用同一个通信对象造成数据重叠
var commPidConnMap = gmap.NewIntAnyMap(true)
// 获取指定进程的通信文件地址
func getCommFilePath(pid int) string {
return getCommDirPath() + gfile.Separator + gconv.String(pid)
// MsgResponse is the response structure for process communication.
type MsgResponse struct {
Code int // 1: OK; Other: Error.
Message string // Response message.
Data []byte // Response data.
}
// 获取进程间通信目录地址
func getCommDirPath() string {
tempDir := os.Getenv(gPROC_TEMP_DIR_ENV_KEY)
if tempDir == "" {
tempDir = gfile.TempDir()
const (
gPROC_COMM_DEFAULT_GRUOP_NAME = "" // Default group name.
gPROC_DEFAULT_TCP_PORT = 10000 // Starting port number for receiver listening.
gPROC_MSG_QUEUE_MAX_LENGTH = 10000 // Max size for each message queue of the group.
)
var (
// commReceiveQueues is the group name to queue map for storing received data.
// The value of the map is type of *gqueue.Queue.
commReceiveQueues = gmap.NewStrAnyMap(true)
// commPidFolderPath specifies the folder path storing pid to port mapping files.
commPidFolderPath = gfile.Join(gfile.TempDir(), "gproc")
)
func init() {
// Automatically create the storage folder.
if !gfile.Exists(commPidFolderPath) {
err := gfile.Mkdir(commPidFolderPath)
if err != nil {
panic(fmt.Errorf(`create gproc folder failed: %v`, err))
}
}
return tempDir + gfile.Separator + "gproc"
}
// getConnByPid creates and returns a TCP connection for specified pid.
func getConnByPid(pid int) (*gtcp.PoolConn, error) {
port := getPortByPid(pid)
if port > 0 {
if conn, err := gtcp.NewPoolConn(fmt.Sprintf("127.0.0.1:%d", port)); err == nil {
return conn, nil
} else {
return nil, err
}
}
return nil, errors.New(fmt.Sprintf("could not find port for pid: %d", pid))
}
// getPortByPid returns the listening port for specified pid.
// It returns 0 if no port found for the specified pid.
func getPortByPid(pid int) int {
path := getCommFilePath(pid)
content := gfcache.GetContents(path)
return gconv.Int(content)
}
// getCommFilePath returns the pid to port mapping file path for given pid.
func getCommFilePath(pid int) string {
return gfile.Join(commPidFolderPath, gconv.String(pid))
}

View File

@ -4,8 +4,6 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// "不要通过共享内存来通信,而应该通过通信来共享内存"
package gproc
import (
@ -21,50 +19,39 @@ import (
"github.com/gogf/gf/util/gconv"
)
const (
gPROC_DEFAULT_TCP_PORT = 10000 // 默认开始监听的TCP端口号如果占用则递增
gPROC_MSG_QUEUE_MAX_LENGTH = 10000 // 进程消息队列最大长度(每个分组)
)
var (
// 是否已开启TCP端口监听服务
// tcpListened marks whether the receiving listening service started.
tcpListened = gtype.NewBool()
)
// 获取其他进程传递到当前进程的消息包,阻塞执行。
// 进程只有在执行该方法后才会打开请求端口,默认情况下不允许进程间通信。
func Receive(group ...string) *Msg {
// 一个进程只能开启一个监听goroutine
if !tcpListened.Val() && tcpListened.Set(true) == false {
go startTcpListening()
// Receive blocks and receives message from other process using local TCP listening.
// Note that, it only enables the TCP listening service when this function called.
func Receive(group ...string) *MsgRequest {
// Use atomic operations to guarantee only one receiver goroutine listening.
if tcpListened.Cas(false, true) {
go receiveTcpListening()
}
queue := (*gqueue.Queue)(nil)
groupName := gPROC_COMM_DEFAULT_GRUOP_NAME
var groupName string
if len(group) > 0 {
groupName = group[0]
}
if v := commReceiveQueues.Get(groupName); v == nil {
commReceiveQueues.LockFunc(func(m map[string]interface{}) {
if v, ok := m[groupName]; ok {
queue = v.(*gqueue.Queue)
} else {
queue = gqueue.New(gPROC_MSG_QUEUE_MAX_LENGTH)
m[groupName] = queue
}
})
} else {
queue = v.(*gqueue.Queue)
groupName = gPROC_COMM_DEFAULT_GRUOP_NAME
}
queue := commReceiveQueues.GetOrSetFuncLock(groupName, func() interface{} {
return gqueue.New(gPROC_MSG_QUEUE_MAX_LENGTH)
}).(*gqueue.Queue)
// Blocking receiving.
if v := queue.Pop(); v != nil {
return v.(*Msg)
return v.(*MsgRequest)
}
return nil
}
// 创建本地进程TCP通信服务
func startTcpListening() {
// receiveTcpListening scans local for available port and starts listening.
func receiveTcpListening() {
var listen *net.TCPListener
// Scan the available port for listening.
for i := gPROC_DEFAULT_TCP_PORT; ; i++ {
addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("127.0.0.1:%d", i))
if err != nil {
@ -74,57 +61,67 @@ func startTcpListening() {
if err != nil {
continue
}
// 将监听的端口保存到通信文件中(字符串类型存放)
// Save the port to the pid file.
if err := gfile.PutContents(getCommFilePath(Pid()), gconv.String(i)); err != nil {
glog.Error(err)
panic(err)
}
break
}
// Start listening.
for {
if conn, err := listen.Accept(); err != nil {
glog.Error(err)
} else if conn != nil {
go tcpServiceHandler(gtcp.NewConnByNetConn(conn))
go receiveTcpHandler(gtcp.NewConnByNetConn(conn))
}
}
}
// TCP数据通信处理回调函数
func tcpServiceHandler(conn *gtcp.Conn) {
option := gtcp.PkgOption{
Retry: gtcp.Retry{
Count: 3,
Interval: 10,
},
}
// receiveTcpHandler is the connection handler for receiving data.
func receiveTcpHandler(conn *gtcp.Conn) {
var result []byte
var response MsgResponse
for {
var result []byte
buffer, err := conn.RecvPkg(option)
response.Code = 0
response.Message = ""
response.Data = nil
buffer, err := conn.RecvPkg()
if len(buffer) > 0 {
msg := new(Msg)
// Package decoding.
msg := new(MsgRequest)
if err := json.Unmarshal(buffer, msg); err != nil {
glog.Error(err)
continue
}
if v := commReceiveQueues.Get(msg.Group); v == nil {
result = []byte(fmt.Sprintf("group [%s] does not exist", msg.Group))
break
if msg.RecvPid != Pid() {
// Not mine package.
response.Message = fmt.Sprintf("receiver pid not match, target: %d, current: %d", msg.RecvPid, Pid())
} else if v := commReceiveQueues.Get(msg.Group); v == nil {
// Group check.
response.Message = fmt.Sprintf("group [%s] does not exist", msg.Group)
} else {
result = []byte("ok")
if v := commReceiveQueues.Get(msg.Group); v != nil {
v.(*gqueue.Queue).Push(msg)
}
// Push to buffer queue.
response.Code = 1
v.(*gqueue.Queue).Push(msg)
}
} else {
// Empty package.
response.Message = "empty package"
}
if err == nil {
if err := conn.SendPkg(result, option); err != nil {
result, err = json.Marshal(response)
if err != nil {
glog.Error(err)
}
if err := conn.SendPkg(result); err != nil {
glog.Error(err)
}
} else {
// Just close the connection if any error occurs.
if err := conn.Close(); err != nil {
glog.Error(err)
}
return
break
}
}
}

View File

@ -7,28 +7,15 @@
package gproc
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"time"
"github.com/gogf/gf/net/gtcp"
"github.com/gogf/gf/os/gfcache"
"github.com/gogf/gf/util/gconv"
"io"
)
const (
gPROC_COMM_FAILURE_RETRY_COUNT = 3 // 失败重试次数
gPROC_COMM_FAILURE_RETRY_TIMEOUT = 1000 // (毫秒)失败重试间隔
gPROC_COMM_SEND_TIMEOUT = 5000 // (毫秒)发送超时时间
gPROC_COMM_DEFAULT_GRUOP_NAME = "" // 默认分组名称
)
// 向指定gproc进程发送数据.
// Send sends data to specified process of given pid.
func Send(pid int, data []byte, group ...string) error {
msg := Msg{
msg := MsgRequest{
SendPid: Pid(),
RecvPid: pid,
Group: gPROC_COMM_DEFAULT_GRUOP_NAME,
@ -41,49 +28,31 @@ func Send(pid int, data []byte, group ...string) error {
if err != nil {
return err
}
var buf []byte
var conn *gtcp.PoolConn
// 循环获取连接TCP对象
for i := gPROC_COMM_FAILURE_RETRY_COUNT; i > 0; i-- {
if conn, err = getConnByPid(pid); err == nil {
break
}
time.Sleep(gPROC_COMM_FAILURE_RETRY_TIMEOUT * time.Millisecond)
}
if conn == nil {
conn, err = getConnByPid(pid)
if err != nil {
return err
}
defer conn.Close()
// 执行数据发送
buf, err = conn.SendRecvPkgWithTimeout(msgBytes, gPROC_COMM_SEND_TIMEOUT*time.Millisecond)
if len(buf) > 0 {
if !bytes.EqualFold(buf, []byte("ok")) {
err = errors.New(string(buf))
// Do the sending.
var result []byte
result, err = conn.SendRecvPkg(msgBytes, gtcp.PkgOption{
Retry: gtcp.Retry{
Count: 3,
},
})
if len(result) > 0 {
response := new(MsgResponse)
err = json.Unmarshal(result, response)
if err == nil {
if response.Code != 1 {
err = errors.New(response.Message)
}
}
}
// EOF不算异常错误
// EOF is not really an error.
if err == io.EOF {
err = nil
}
return err
}
// 获取指定进程的TCP通信对象
func getConnByPid(pid int) (*gtcp.PoolConn, error) {
port := getPortByPid(pid)
if port > 0 {
if conn, err := gtcp.NewPoolConn(fmt.Sprintf("127.0.0.1:%d", port)); err == nil {
return conn, nil
} else {
return nil, err
}
}
return nil, errors.New(fmt.Sprintf("could not find port for pid: %d", pid))
}
// 获取指定进程监听的端口号
func getPortByPid(pid int) int {
path := getCommFilePath(pid)
content := gfcache.GetContents(path)
return gconv.Int(content)
}

View File

@ -4,7 +4,6 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// 进程管理.
package gproc
import (

View File

@ -14,26 +14,23 @@ import (
"strings"
)
// 子进程
// Process is the struct for a single process.
type Process struct {
exec.Cmd
Manager *Manager // 所属进程管理器
PPid int // 自定义关联的父进程ID
Manager *Manager
PPid int
}
// 创建一个进程(不执行)
// NewProcess creates and returns a new Process.
func NewProcess(path string, args []string, environment ...[]string) *Process {
var env []string
if len(environment) > 0 {
env = make([]string, 0)
for _, v := range environment[0] {
env = append(env, v)
}
env = make([]string, len(environment[0]))
copy(env, environment[0])
} else {
env = os.Environ()
}
env = append(env, fmt.Sprintf("%s=%s", gPROC_TEMP_DIR_ENV_KEY, os.TempDir()))
p := &Process{
process := &Process{
Manager: nil,
PPid: os.Getpid(),
Cmd: exec.Cmd{
@ -46,21 +43,25 @@ func NewProcess(path string, args []string, environment ...[]string) *Process {
ExtraFiles: make([]*os.File, 0),
},
}
// 当前工作目录
if d, err := os.Getwd(); err == nil {
p.Dir = d
}
process.Dir, _ = os.Getwd()
if len(args) > 0 {
// Exclude of current binary path.
start := 0
if strings.EqualFold(path, args[0]) {
start = 1
}
p.Args = append(p.Args, args[start:]...)
process.Args = append(process.Args, args[start:]...)
}
return p
return process
}
// 开始执行(非阻塞)
// NewProcessCmd creates and returns a process with given command and optional environment variable array.
func NewProcessCmd(cmd string, environment ...[]string) *Process {
return NewProcess(getShell(), append([]string{getShellOption()}, parseCommand(cmd)...), environment...)
}
// Start starts executing the process in non-blocking way.
// It returns the pid if success, or else it returns an error.
func (p *Process) Start() (int, error) {
if p.Process != nil {
return p.Pid(), nil
@ -76,7 +77,7 @@ func (p *Process) Start() (int, error) {
}
}
// 运行进程(阻塞等待执行完毕)
// Run executes the process in blocking way.
func (p *Process) Run() error {
if _, err := p.Start(); err == nil {
return p.Wait()
@ -93,7 +94,7 @@ func (p *Process) Pid() int {
return 0
}
// 向进程发送消息
// Send send custom data to the process.
func (p *Process) Send(data []byte) error {
if p.Process != nil {
return Send(p.Process.Pid, data)
@ -114,6 +115,8 @@ func (p *Process) Kill() error {
if p.Manager != nil {
p.Manager.processes.Remove(p.Pid())
}
p.Process.Release()
p.Process.Wait()
return nil
} else {
return err

View File

@ -35,6 +35,8 @@ func New(ttl time.Duration, storage ...Storage) *Manager {
}
// New creates or fetches the session for given session id.
// The parameter <sessionId> is optional, it creates a new one if not it's passed
// depending on Storage.New.
func (m *Manager) New(sessionId ...string) *Session {
var id string
if len(sessionId) > 0 && sessionId[0] != "" {

View File

@ -8,6 +8,7 @@ package gsession
import (
"encoding/json"
"fmt"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/internal/intlog"
"os"
@ -22,8 +23,6 @@ import (
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/os/glog"
"github.com/gogf/gf/os/gfile"
)
@ -39,7 +38,7 @@ var (
DefaultStorageFilePath = gfile.Join(gfile.TempDir(), "gsessions")
DefaultStorageFileCryptoKey = []byte("Session storage file crypto key!")
DefaultStorageFileCryptoEnabled = false
DefaultStorageFileLoopInterval = time.Minute
DefaultStorageFileLoopInterval = 10 * time.Second
)
func init() {
@ -55,15 +54,15 @@ func NewStorageFile(path ...string) *StorageFile {
if len(path) > 0 && path[0] != "" {
storagePath, _ = gfile.Search(path[0])
if storagePath == "" {
glog.Panicf("'%s' does not exist", path[0])
panic(fmt.Sprintf(fmt.Sprintf("'%s' does not exist", path[0])))
}
if !gfile.IsWritable(storagePath) {
glog.Panicf("'%s' is not writable", path[0])
panic(fmt.Sprintf("'%s' is not writable", path[0]))
}
}
if storagePath != "" {
if err := gfile.Mkdir(storagePath); err != nil {
glog.Panicf("mkdir '%s' failed: %v", path[0], err)
panic(fmt.Sprintf("mkdir '%s' failed: %v", path[0], err))
}
}
s := &StorageFile{

View File

@ -96,24 +96,48 @@ func SetTimeZone(zone string) error {
return err
}
// Nanosecond returns the timestamp in nanoseconds.
func Nanosecond() int64 {
return time.Now().UnixNano()
// Timestamp returns the timestamp in seconds.
func Timestamp() int64 {
return Now().Timestamp()
}
// Microsecond returns the timestamp in microseconds.
func Microsecond() int64 {
return time.Now().UnixNano() / 1e3
// TimestampMilli returns the timestamp in milliseconds.
func TimestampMilli() int64 {
return Now().TimestampMilli()
}
// Millisecond returns the timestamp in milliseconds.
func Millisecond() int64 {
return time.Now().UnixNano() / 1e6
// TimestampMicro returns the timestamp in microseconds.
func TimestampMicro() int64 {
return Now().TimestampMicro()
}
// TimestampNano returns the timestamp in nanoseconds.
func TimestampNano() int64 {
return Now().TimestampNano()
}
// Second returns the timestamp in seconds.
// Deprecated, use Timestamp instead.
func Second() int64 {
return time.Now().Unix()
return Timestamp()
}
// Millisecond returns the timestamp in milliseconds.
// Deprecated, use TimestampMilli instead.
func Millisecond() int64 {
return TimestampMilli()
}
// Microsecond returns the timestamp in microseconds.
// Deprecated, use TimestampMicro instead.
func Microsecond() int64 {
return TimestampMicro()
}
// Nanosecond returns the timestamp in nanoseconds.
// Deprecated, use TimestampNano instead.
func Nanosecond() int64 {
return TimestampNano()
}
// Date returns current date in string like "2006-01-02".

View File

@ -126,7 +126,12 @@ func (t *Time) Format(format string) string {
return buffer.String()
}
// FormatTo formats and returns a new Time object with given custom <format>.
// FormatNew formats and returns a new Time object with given custom <format>.
func (t *Time) FormatNew(format string) *Time {
return NewFromStr(t.Format(format))
}
// FormatTo formats <t> with given custom <format>.
func (t *Time) FormatTo(format string) *Time {
t.Time = NewFromStr(t.Format(format)).Time
return t
@ -137,7 +142,12 @@ func (t *Time) Layout(layout string) string {
return t.Time.Format(layout)
}
// Layout formats the time with stdlib layout and returns the new Time object.
// LayoutNew formats the time with stdlib layout and returns the new Time object.
func (t *Time) LayoutNew(layout string) *Time {
return NewFromStr(t.Layout(layout))
}
// LayoutTo formats <t> with stdlib layout.
func (t *Time) LayoutTo(layout string) *Time {
t.Time = NewFromStr(t.Layout(layout)).Time
return t

View File

@ -78,24 +78,48 @@ func NewFromTimeStamp(timestamp int64) *Time {
}
}
// Second returns the timestamp in seconds.
func (t *Time) Second() int64 {
// Timestamp returns the timestamp in seconds.
func (t *Time) Timestamp() int64 {
return t.UnixNano() / 1e9
}
// Nanosecond returns the timestamp in nanoseconds.
func (t *Time) Nanosecond() int64 {
return t.UnixNano()
// TimestampMilli returns the timestamp in milliseconds.
func (t *Time) TimestampMilli() int64 {
return t.UnixNano() / 1e6
}
// Microsecond returns the timestamp in microseconds.
func (t *Time) Microsecond() int64 {
// TimestampMicro returns the timestamp in microseconds.
func (t *Time) TimestampMicro() int64 {
return t.UnixNano() / 1e3
}
// Millisecond returns the timestamp in milliseconds.
func (t *Time) Millisecond() int64 {
return t.UnixNano() / 1e6
// TimestampNano returns the timestamp in nanoseconds.
func (t *Time) TimestampNano() int64 {
return t.UnixNano()
}
// Second returns the second offset within the minute specified by t,
// in the range [0, 59].
func (t *Time) Second() int {
return t.Time.Second()
}
// Millisecond returns the millisecond offset within the second specified by t,
// in the range [0, 999].
func (t *Time) Millisecond() int {
return t.Time.Nanosecond() / 1e6
}
// Microsecond returns the microsecond offset within the second specified by t,
// in the range [0, 999999].
func (t *Time) Microsecond() int {
return t.Time.Nanosecond() / 1e3
}
// Nanosecond returns the nanosecond offset within the second specified by t,
// in the range [0, 999999999].
func (t *Time) Nanosecond() int {
return t.Time.Nanosecond()
}
// String returns current time object as string.
@ -196,6 +220,33 @@ func (t *Time) Truncate(d time.Duration) *Time {
return t
}
// Equal reports whether t and u represent the same time instant.
// Two times can be equal even if they are in different locations.
// For example, 6:00 +0200 CEST and 4:00 UTC are Equal.
// See the documentation on the Time type for the pitfalls of using == with
// Time values; most code should use Equal instead.
func (t *Time) Equal(u *Time) bool {
return t.Time.Equal(u.Time)
}
// Before reports whether the time instant t is before u.
func (t *Time) Before(u *Time) bool {
return t.Time.Before(u.Time)
}
// After reports whether the time instant t is after u.
func (t *Time) After(u *Time) bool {
return t.Time.After(u.Time)
}
// Sub returns the duration t-u. If the result exceeds the maximum (or minimum)
// value that can be stored in a Duration, the maximum (or minimum) duration
// will be returned.
// To compute t-d for a duration d, use t.Add(-d).
func (t *Time) Sub(u *Time) time.Duration {
return t.Time.Sub(u.Time)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (t *Time) MarshalJSON() ([]byte, error) {
return []byte(`"` + t.String() + `"`), nil

View File

@ -73,28 +73,28 @@ func Test_NewFromTimeStamp(t *testing.T) {
func Test_Time_Second(t *testing.T) {
gtest.Case(t, func() {
timeTemp := gtime.Now()
gtest.Assert(timeTemp.Second(), timeTemp.Time.Unix())
gtest.Assert(timeTemp.Second(), timeTemp.Time.Second())
})
}
func Test_Time_Nanosecond(t *testing.T) {
gtest.Case(t, func() {
timeTemp := gtime.Now()
gtest.Assert(timeTemp.Nanosecond(), timeTemp.Time.UnixNano())
gtest.Assert(timeTemp.Nanosecond(), timeTemp.Time.Nanosecond())
})
}
func Test_Time_Microsecond(t *testing.T) {
gtest.Case(t, func() {
timeTemp := gtime.Now()
gtest.Assert(timeTemp.Microsecond(), timeTemp.Time.UnixNano()/1e3)
gtest.Assert(timeTemp.Microsecond(), timeTemp.Time.Nanosecond()/1e3)
})
}
func Test_Time_Millisecond(t *testing.T) {
gtest.Case(t, func() {
timeTemp := gtime.Now()
gtest.Assert(timeTemp.Millisecond(), timeTemp.Time.UnixNano()/1e6)
gtest.Assert(timeTemp.Millisecond(), timeTemp.Time.Nanosecond()/1e6)
})
}

Some files were not shown because too many files have changed in this diff Show More