mirror of
https://gitee.com/johng/gf
synced 2026-06-19 14:52:56 +08:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d53d760d8 | |||
| 922e720d63 | |||
| 50018773b7 | |||
| df99036d41 | |||
| ae0fa888f0 | |||
| 18892fb66d | |||
| 5f2be10563 | |||
| a5a88222a6 | |||
| 4facdd5c9e | |||
| 76bc9bd385 | |||
| 364452f3bb | |||
| 4996755f11 | |||
| e33230a88f | |||
| 951ce46932 | |||
| 795c7395e6 | |||
| 27cf47bcd3 | |||
| 58a25c6f61 | |||
| 2d754f80b1 | |||
| f4e8fbe767 | |||
| 458318d374 | |||
| e3f54e1353 | |||
| 4374996073 | |||
| 87295ef1fe | |||
| 81d4082b6a | |||
| d7e19bc3f3 | |||
| 34ef0ea792 | |||
| 2804834540 | |||
| add7dd5a45 | |||
| e40894ca45 | |||
| 28825f5395 | |||
| 6ca5141020 | |||
| fe4f8e1810 |
@ -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())
|
||||
|
||||
10
.example/debug/gdebug/gdebug_info.go
Normal file
10
.example/debug/gdebug/gdebug_info.go
Normal file
@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(gdebug.BuildInfo())
|
||||
}
|
||||
3
.example/frame/mvc/app/model/article/article.go
Normal file
3
.example/frame/mvc/app/model/article/article.go
Normal file
@ -0,0 +1,3 @@
|
||||
package article
|
||||
|
||||
// Fill with you ideas below.
|
||||
68
.example/frame/mvc/app/model/article/article_entity.go
Normal file
68
.example/frame/mvc/app/model/article/article_entity.go
Normal 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()
|
||||
}
|
||||
362
.example/frame/mvc/app/model/article/article_model.go
Normal file
362
.example/frame/mvc/app/model/article/article_model.go
Normal 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)
|
||||
})
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
@ -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]
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
9
.example/os/gbuild/config.toml
Normal file
9
.example/os/gbuild/config.toml
Normal 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"
|
||||
11
.example/os/gbuild/gbuild.go
Normal file
11
.example/os/gbuild/gbuild.go
Normal 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())
|
||||
}
|
||||
@ -1,6 +1,3 @@
|
||||
// 多进程通信示例,
|
||||
// 子进程每个1秒向父进程发送当前时间,
|
||||
// 父进程监听进程消息,收到后打印到终端。
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
@ -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)
|
||||
}
|
||||
12
.example/os/gproc/gproc_sleep.go
Normal file
12
.example/os/gproc/gproc_sleep.go
Normal 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)
|
||||
}
|
||||
@ -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"
|
||||
@ -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("")
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
63
DONATOR.MD
63
DONATOR.MD
@ -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|
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 @@
|
||||
* 简便及可维护性为宗旨;
|
||||
* 详尽的开发文档及示例;
|
||||
* 完善的本地中文化支持;
|
||||
* 致力于项目的通用方案;
|
||||
* 更适合企业及团队使用;
|
||||
* 更多请查阅文档及源码;
|
||||
|
||||
|
||||
@ -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...)
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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]
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
// 动态切换数据库
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取是否开启调试服务
|
||||
|
||||
@ -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
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
77
database/gdb/gdb_unit_z_func_test.go
Normal file
77
database/gdb/gdb_unit_z_func_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -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")
|
||||
})
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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())
|
||||
}
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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
|
||||
})
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
|
||||
package gmvc
|
||||
|
||||
// MVC模型基类
|
||||
// Model is the base struct for model.
|
||||
type Model struct {
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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"}
|
||||
)
|
||||
|
||||
@ -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...)
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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.
|
||||
}
|
||||
|
||||
|
||||
@ -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...)
|
||||
}
|
||||
|
||||
@ -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...)
|
||||
}
|
||||
|
||||
@ -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...)
|
||||
}
|
||||
|
||||
@ -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...)
|
||||
}
|
||||
|
||||
@ -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...)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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...)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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")
|
||||
})
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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...)
|
||||
|
||||
@ -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))
|
||||
|
||||
21
net/gtcp/gtcp_unit_init_test.go
Normal file
21
net/gtcp/gtcp_unit_init_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
172
net/gtcp/gtcp_unit_pkg_test.go
Normal file
172
net/gtcp/gtcp_unit_pkg_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
172
net/gtcp/gtcp_unit_pool_pkg_test.go
Normal file
172
net/gtcp/gtcp_unit_pool_pkg_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
65
net/gtcp/gtcp_unit_pool_test.go
Normal file
65
net/gtcp/gtcp_unit_pool_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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
72
os/gbuild/gbuild.go
Normal 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
|
||||
}
|
||||
@ -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{} {
|
||||
|
||||
@ -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{} {
|
||||
|
||||
@ -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...)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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
|
||||
@ -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] != "" {
|
||||
|
||||
@ -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{
|
||||
|
||||
@ -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".
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
Reference in New Issue
Block a user