├── UnitTesting └── init.go ├── Client ├── MaxMind │ ├── Init.go │ ├── Adapter.go │ └── Client.go ├── Db │ ├── Init.go │ ├── Row.go │ ├── Stmt.go │ ├── Client.go │ └── Adapter.go ├── Memory │ ├── Init.go │ ├── Adapter.go │ └── Client.go ├── Http │ ├── Init.go │ ├── Option.go │ ├── Adapter.go │ └── Client.go ├── Redis │ ├── ClusterPool.go │ ├── Init.go │ ├── Conn.go │ ├── Adapter.go │ ├── Client.go │ └── MasterSlavePool.go ├── Mongo │ ├── Init.go │ └── Client.go ├── RabbitMq │ ├── Init.go │ ├── ChannelBox.go │ ├── Adapter.go │ ├── ConnBox.go │ ├── Client.go │ └── Pool.go └── Memcache │ ├── Init.go │ ├── Adapter.go │ ├── Client.go │ └── Pool.go ├── go.mod ├── Object.go ├── Exception.go ├── StopBefore.go ├── Interface.go ├── Status.go ├── Util ├── Slice.go ├── String.go ├── Map.go ├── Struct.go ├── Yaml.go ├── Hash.go ├── Path.go ├── Conv.go └── Misc.go ├── File.go ├── Response.go ├── gotest.sh ├── makefile ├── Router.go ├── Gzip.go ├── I18n.go ├── View.go ├── Controller.go ├── Container.go ├── Value.go ├── Init.go ├── Config.go └── Application.go /UnitTesting/init.go: -------------------------------------------------------------------------------- 1 | package UnitTesting 2 | 3 | import ( 4 | "github.com/pinguo/pgo" 5 | ) 6 | 7 | var TestObj *pgo.Object 8 | 9 | func init() { 10 | TestObj = GetTestObj() 11 | } 12 | 13 | func GetTestObj() *pgo.Object { 14 | TestObj := &pgo.Object{} 15 | ctx := &pgo.Context{} 16 | TestObj.SetContext(ctx) 17 | return TestObj 18 | } 19 | -------------------------------------------------------------------------------- /Client/MaxMind/Init.go: -------------------------------------------------------------------------------- 1 | package MaxMind 2 | 3 | import "github.com/pinguo/pgo" 4 | 5 | const ( 6 | DBCountry = 0 7 | DBCity = 1 8 | AdapterClass = "@pgo/Client/MaxMind/Adapter" 9 | 10 | defaultComponentId = "maxMind" 11 | defaultLang = "en" 12 | ) 13 | 14 | func init() { 15 | container := pgo.App.GetContainer() 16 | container.Bind(&Adapter{}) 17 | container.Bind(&Client{}) 18 | } 19 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pinguo/pgo 2 | 3 | require ( 4 | github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 5 | github.com/go-yaml/yaml v2.1.0+incompatible 6 | github.com/kr/pretty v0.1.0 // indirect 7 | github.com/oschwald/maxminddb-golang v1.3.1 8 | github.com/stretchr/testify v1.3.0 // indirect 9 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 10 | gopkg.in/yaml.v2 v2.2.2 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /Client/Db/Init.go: -------------------------------------------------------------------------------- 1 | package Db 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/pinguo/pgo" 8 | ) 9 | 10 | const ( 11 | AdapterClass = "@pgo/Client/Db/Adapter" 12 | 13 | defaultComponentId = "db" 14 | defaultTimeout = 10 * time.Second 15 | ) 16 | 17 | var ( 18 | stmtPool sync.Pool 19 | rowPool sync.Pool 20 | ) 21 | 22 | func init() { 23 | container := pgo.App.GetContainer() 24 | 25 | container.Bind(&Adapter{}) 26 | container.Bind(&Client{}) 27 | 28 | stmtPool.New = func() interface{} { return &Stmt{} } 29 | rowPool.New = func() interface{} { return &Row{} } 30 | } 31 | -------------------------------------------------------------------------------- /Client/Memory/Init.go: -------------------------------------------------------------------------------- 1 | package Memory 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/pinguo/pgo" 7 | ) 8 | 9 | const ( 10 | AdapterClass = "@pgo/Client/Memory/Adapter" 11 | 12 | defaultComponentId = "memory" 13 | defaultGcMaxItems = 1000 14 | defaultGcInterval = 60 * time.Second 15 | defaultExpire = 60 * time.Second 16 | 17 | minGcInterval = 10 * time.Second 18 | maxGcInterval = 600 * time.Second 19 | 20 | errSetProp = "memory: failed to set %s, %s" 21 | ) 22 | 23 | func init() { 24 | container := pgo.App.GetContainer() 25 | 26 | container.Bind(&Adapter{}) 27 | container.Bind(&Client{}) 28 | } 29 | -------------------------------------------------------------------------------- /Client/Http/Init.go: -------------------------------------------------------------------------------- 1 | package Http 2 | 3 | import ( 4 | "net/url" 5 | "time" 6 | 7 | "github.com/pinguo/pgo" 8 | ) 9 | 10 | const ( 11 | AdapterClass = "@pgo/Client/Http/Adapter" 12 | 13 | defaultComponentId = "http" 14 | defaultUserAgent = "PGO Framework" 15 | defaultTimeout = 10 * time.Second 16 | ) 17 | 18 | func init() { 19 | container := pgo.App.GetContainer() 20 | 21 | container.Bind(&Adapter{}) 22 | container.Bind(&Client{}) 23 | } 24 | 25 | func baseUrl(addr string) string { 26 | u, e := url.Parse(addr) 27 | if e != nil { 28 | panic("http parse url failed, " + e.Error()) 29 | } 30 | 31 | return u.Scheme + "://" + u.Host + u.Path 32 | } 33 | -------------------------------------------------------------------------------- /Client/Db/Row.go: -------------------------------------------------------------------------------- 1 | package Db 2 | 3 | import ( 4 | "database/sql" 5 | 6 | "github.com/pinguo/pgo" 7 | ) 8 | 9 | // Row wrapper for sql.Row 10 | type Row struct { 11 | pgo.Object 12 | row *sql.Row 13 | query string 14 | args []interface{} 15 | } 16 | 17 | func (r *Row) close() { 18 | r.SetContext(nil) 19 | r.row = nil 20 | r.query = "" 21 | r.args = nil 22 | rowPool.Put(r) 23 | } 24 | 25 | // Scan copies the columns in the current row into the values pointed at by dest. 26 | func (r *Row) Scan(dest ...interface{}) error { 27 | err := r.row.Scan(dest...) 28 | if err != nil && err != sql.ErrNoRows { 29 | r.GetContext().Error("Db.QueryOne error, %s, query:%s, args:%v", err.Error(), r.query, r.args) 30 | } 31 | 32 | r.close() 33 | return err 34 | } 35 | -------------------------------------------------------------------------------- /Object.go: -------------------------------------------------------------------------------- 1 | package pgo 2 | 3 | // Object base class of context based object 4 | type Object struct { 5 | context *Context 6 | } 7 | 8 | // GetContext get context of this object 9 | func (o *Object) GetContext() *Context { 10 | return o.context 11 | } 12 | 13 | // SetContext set context of this object 14 | func (o *Object) SetContext(ctx *Context) { 15 | o.context = ctx 16 | } 17 | 18 | // GetObject create new object and inject context 19 | func (o *Object) GetObject(class interface{}, params ...interface{}) interface{} { 20 | params = append(params, o.GetContext()) 21 | return CreateObject(class, params...) 22 | } 23 | 24 | // GetObject create new object and inject custom context 25 | func (o *Object) GetObjectCtx(class interface{}, context *Context, params ...interface{}) interface{} { 26 | params = append(params, context) 27 | return CreateObject(class, params...) 28 | } -------------------------------------------------------------------------------- /Client/Http/Option.go: -------------------------------------------------------------------------------- 1 | package Http 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | ) 7 | 8 | // Option config option for http request 9 | type Option struct { 10 | Header http.Header 11 | Cookies []*http.Cookie 12 | Timeout time.Duration 13 | } 14 | 15 | // SetHeader set request header for the current request 16 | func (o *Option) SetHeader(name, value string) *Option { 17 | if o.Header == nil { 18 | o.Header = make(http.Header) 19 | } 20 | 21 | o.Header.Set(name, value) 22 | return o 23 | } 24 | 25 | // SetCookie set request cookie for the current request 26 | func (o *Option) SetCookie(name, value string) *Option { 27 | o.Cookies = append(o.Cookies, &http.Cookie{Name: name, Value: value}) 28 | return o 29 | } 30 | 31 | // SetTimeout set request timeout for the current request 32 | func (o *Option) SetTimeout(timeout time.Duration) *Option { 33 | o.Timeout = timeout 34 | return o 35 | } 36 | -------------------------------------------------------------------------------- /Client/Redis/ClusterPool.go: -------------------------------------------------------------------------------- 1 | package Redis 2 | 3 | func newClusterPool(pool *Pool) interface{} { 4 | return &ClusterPool{ 5 | pool: pool, 6 | } 7 | } 8 | 9 | type ClusterPool struct { 10 | pool *Pool 11 | } 12 | 13 | func (c *ClusterPool) getAddrByKey(cmd, key, prevDft string) string { 14 | c.pool.lock.RLock() 15 | defer c.pool.lock.RUnlock() 16 | return c.pool.hashRing.GetNode(key) 17 | } 18 | 19 | // first init Cluster 20 | func (c *ClusterPool) startCheck() { 21 | for addr, item := range c.pool.servers { 22 | c.pool.hashRing.AddNode(addr, item.weight) 23 | } 24 | } 25 | 26 | // timing check cluster 27 | func (c *ClusterPool) check(addr, aType string) { 28 | c.pool.lock.Lock() 29 | defer c.pool.lock.Unlock() 30 | if aType == NodeActionAdd { 31 | c.pool.hashRing.AddNode(addr, c.pool.servers[addr].weight) 32 | } else if aType == NodeActionDel { 33 | c.pool.hashRing.DelNode(addr) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Client/Mongo/Init.go: -------------------------------------------------------------------------------- 1 | package Mongo 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/pinguo/pgo" 7 | ) 8 | 9 | const ( 10 | AdapterClass = "@pgo/Client/Mongo/Adapter" 11 | 12 | defaultComponentId = "mongo" 13 | defaultDsn = "mongodb://127.0.0.1:27017/" 14 | defaultOptions = "connect=replicaSet&maxPoolSize=100&minPoolSize=1&maxIdleTimeMS=300000" + 15 | "&ssl=false&w=1&j=false&wtimeoutMS=10000&readPreference=secondaryPreferred" 16 | 17 | defaultConnectTimeout = 1 * time.Second 18 | defaultReadTimeout = 10 * time.Second 19 | defaultWriteTimeout = 10 * time.Second 20 | 21 | errSetProp = "mongo: failed to set %s, %s" 22 | errInvalidDsn = "mongo: invalid dsn %s, %s" 23 | errInvalidOpt = "mongo: invalid option " 24 | errDialFailed = "mongo: failed to dial %s, %s" 25 | ) 26 | 27 | func init() { 28 | container := pgo.App.GetContainer() 29 | 30 | container.Bind(&Adapter{}) 31 | container.Bind(&Client{}) 32 | } 33 | -------------------------------------------------------------------------------- /Exception.go: -------------------------------------------------------------------------------- 1 | package pgo 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // NewException create new exception with status and message 8 | func NewException(status int, msg ...interface{}) *Exception { 9 | message := "" 10 | if len(msg) == 1 { 11 | message = msg[0].(string) 12 | } else if len(msg) > 1 { 13 | message = fmt.Sprintf(msg[0].(string), msg[1:]...) 14 | } 15 | 16 | return &Exception{status, message} 17 | } 18 | 19 | // Exception panic as exception 20 | type Exception struct { 21 | status int 22 | message string 23 | } 24 | 25 | // GetStatus get exception status code 26 | func (e *Exception) GetStatus() int { 27 | return e.status 28 | } 29 | 30 | // GetMessage get exception message string 31 | func (e *Exception) GetMessage() string { 32 | return e.message 33 | } 34 | 35 | // Error implement error interface 36 | func (e *Exception) Error() string { 37 | return fmt.Sprintf("exception: %d, message: %s", e.status, e.message) 38 | } 39 | -------------------------------------------------------------------------------- /Client/MaxMind/Adapter.go: -------------------------------------------------------------------------------- 1 | package MaxMind 2 | 3 | import ( 4 | "github.com/pinguo/pgo" 5 | ) 6 | 7 | // Adapter of MaxMind Client, add context support. 8 | // usage: mmd := this.GetObject(MaxMind.AdapterClass).(*MaxMind.Adapter) 9 | type Adapter struct { 10 | pgo.Object 11 | client *Client 12 | } 13 | 14 | func (a *Adapter) Construct(componentId ...string) { 15 | id := defaultComponentId 16 | if len(componentId) > 0 { 17 | id = componentId[0] 18 | } 19 | 20 | a.client = pgo.App.Get(id).(*Client) 21 | } 22 | 23 | func (a *Adapter) GetClient() *Client { 24 | return a.client 25 | } 26 | 27 | // get geo info by ip, optional args: 28 | // db int: preferred max mind db 29 | // lang string: preferred i18n language 30 | func (a *Adapter) GeoByIp(ip string, args ...interface{}) *Geo { 31 | profile := "GeoByIp:" + ip 32 | a.GetContext().ProfileStart(profile) 33 | defer a.GetContext().ProfileStop(profile) 34 | 35 | return a.client.GeoByIp(ip, args...) 36 | } 37 | -------------------------------------------------------------------------------- /StopBefore.go: -------------------------------------------------------------------------------- 1 | package pgo 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | type StopBefore struct { 8 | performQueue []*Item 9 | } 10 | 11 | type Item struct { 12 | Action reflect.Value 13 | Params []reflect.Value 14 | } 15 | 16 | // 增加停止前执行的对象和方法 17 | func (s *StopBefore) Add(obj interface{}, method string, dfParams ...[]interface{}) { 18 | 19 | if len(s.performQueue) > 10 { 20 | panic("The length of the performQueue cannot be greater than 10") 21 | } 22 | var params = make([]reflect.Value, 0) 23 | if len(dfParams) > 0 { 24 | for _, v := range dfParams[0] { 25 | params = append(params, reflect.ValueOf(v)) 26 | } 27 | } 28 | action := reflect.ValueOf(obj).MethodByName(method) 29 | 30 | if action.IsValid() == false { 31 | panic("err obj or method") 32 | } 33 | s.performQueue = append(s.performQueue, &Item{Action: action, Params: params}) 34 | } 35 | 36 | // 执行 37 | func (s *StopBefore) Exec() { 38 | for _, item := range s.performQueue { 39 | item.Action.Call(item.Params) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Client/RabbitMq/Init.go: -------------------------------------------------------------------------------- 1 | package RabbitMq 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/pinguo/pgo" 7 | ) 8 | 9 | const ( 10 | AdapterClass = "@pgo/Client/RabbitMq/Adapter" 11 | defaultComponentId = "rabbitMq" 12 | dftExchangeType = "direct" 13 | dftExchangeName = "direct_pgo_dft" 14 | dftMaxChannelNum = 2000 15 | dftMaxIdleChannel = 200 16 | dftMaxIdleChannelTime = 60 * time.Second 17 | dftMaxWaitTime = 200 * time.Microsecond 18 | dftProbeInterval = 0 19 | dftProtocol = "amqp" 20 | defaultTimeout = 1 * time.Second 21 | errSetProp = "rabbitMq: failed to set %s, %s" 22 | ) 23 | 24 | func init() { 25 | container := pgo.App.GetContainer() 26 | 27 | container.Bind(&Client{}) 28 | container.Bind(&Adapter{}) 29 | } 30 | 31 | type RabbitHeaders struct { 32 | LogId string 33 | Exchange string 34 | RouteKey string 35 | Service string 36 | OpUid string 37 | Timestamp time.Time 38 | MessageId string 39 | } 40 | 41 | // rabbit 发布结构 42 | type PublishData struct { 43 | OpCode string // 操作code 和queue绑定相关 44 | OpUid string // 操作用户id 可以为空 45 | Data interface{} // 发送数据 46 | } 47 | 48 | type ConsumeData struct { 49 | QueueName string 50 | OpCodes []string 51 | AutoAck bool 52 | NoWait bool 53 | Exclusive bool 54 | Limit int 55 | } 56 | -------------------------------------------------------------------------------- /Interface.go: -------------------------------------------------------------------------------- 1 | package pgo 2 | 3 | import "time" 4 | 5 | type IBind interface { 6 | GetBindInfo(v interface{}) interface{} 7 | } 8 | 9 | type IObject interface { 10 | SetContext(ctx *Context) 11 | GetContext() *Context 12 | GetObject(class interface{}, params ...interface{}) interface{} 13 | } 14 | 15 | type IController interface { 16 | BeforeAction(action string) 17 | AfterAction(action string) 18 | HandlePanic(v interface{}) 19 | } 20 | 21 | type IPlugin interface { 22 | HandleRequest(ctx *Context) 23 | } 24 | 25 | type IEvent interface { 26 | HandleEvent(event string, ctx *Context, args ...interface{}) 27 | } 28 | 29 | type IFormatter interface { 30 | Format(item *LogItem) string 31 | } 32 | 33 | type ITarget interface { 34 | Process(item *LogItem) 35 | Flush(final bool) 36 | } 37 | 38 | type IConfigParser interface { 39 | Parse(path string) map[string]interface{} 40 | } 41 | 42 | type ICache interface { 43 | Get(key string) *Value 44 | MGet(keys []string) map[string]*Value 45 | Set(key string, value interface{}, expire ...time.Duration) bool 46 | MSet(items map[string]interface{}, expire ...time.Duration) bool 47 | Add(key string, value interface{}, expire ...time.Duration) bool 48 | MAdd(items map[string]interface{}, expire ...time.Duration) bool 49 | Del(key string) bool 50 | MDel(keys []string) bool 51 | Exists(key string) bool 52 | Incr(key string, delta int) int 53 | } 54 | -------------------------------------------------------------------------------- /Status.go: -------------------------------------------------------------------------------- 1 | package pgo 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/pinguo/pgo/Util" 8 | ) 9 | 10 | // Status the status component, configuration: 11 | // status: 12 | // useI18n: false 13 | // mapping: 14 | // 11002: "Verify Sign Error" 15 | type Status struct { 16 | useI18n bool 17 | mapping map[int]string 18 | } 19 | 20 | func (s *Status) Construct() { 21 | s.useI18n = false 22 | s.mapping = make(map[int]string) 23 | } 24 | 25 | // SetUseI18n set whether to use i18n translation 26 | func (s *Status) SetUseI18n(useI18n bool) { 27 | s.useI18n = useI18n 28 | } 29 | 30 | // SetMapping set mapping from status code to text 31 | func (s *Status) SetMapping(m map[string]interface{}) { 32 | for k, v := range m { 33 | s.mapping[Util.ToInt(k)] = Util.ToString(v) 34 | } 35 | } 36 | 37 | // GetText get status text 38 | func (s *Status) GetText(status int, ctx *Context, dft ...string) string { 39 | txt, ok := s.mapping[status] 40 | if !ok { 41 | if len(dft) == 0 || len(dft[0]) == 0 { 42 | if txt = http.StatusText(status); len(txt) == 0 { 43 | panic(fmt.Sprintf("unknown status code: %d", status)) 44 | } 45 | } else { 46 | txt = dft[0] 47 | } 48 | } 49 | 50 | if s.useI18n && ctx != nil { 51 | al := ctx.GetHeader("Accept-Language", "") 52 | txt = App.GetI18n().Translate(txt, al) 53 | } 54 | 55 | return txt 56 | } 57 | -------------------------------------------------------------------------------- /Client/RabbitMq/ChannelBox.go: -------------------------------------------------------------------------------- 1 | package RabbitMq 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/pinguo/pgo" 7 | "github.com/streadway/amqp" 8 | ) 9 | 10 | func newChannelBox(connBox *ConnBox, pool *Pool) *ChannelBox { 11 | connBox.newChannelLock.Lock() 12 | defer connBox.newChannelLock.Unlock() 13 | channel, err := connBox.connection.Channel() 14 | 15 | if err != nil { 16 | panic("Rabbit newChannelBox err:" + err.Error()) 17 | } 18 | connBox.useConnCount++ 19 | return &ChannelBox{connBoxId: connBox.id, pool: pool, channel: channel, connStartTime: connBox.startTime, lastActive: time.Now()} 20 | } 21 | 22 | type ChannelBox struct { 23 | connBoxId string 24 | pool *Pool 25 | channel *amqp.Channel 26 | connStartTime time.Time 27 | lastActive time.Time 28 | } 29 | 30 | func (c *ChannelBox) Close(force bool) { 31 | if force || c.connStartTime != c.pool.getConnBox(c.connBoxId).startTime { 32 | c.channelClose() 33 | return 34 | } 35 | 36 | if !c.pool.putFreeChannel(c) { 37 | c.channelClose() 38 | } else { 39 | c.lastActive = time.Now() 40 | } 41 | } 42 | 43 | func (c *ChannelBox) channelClose() { 44 | connBox := c.pool.getConnBox(c.connBoxId) 45 | connBox.useConnCount-- 46 | if connBox.isClosed() == false{ 47 | err := c.channel.Close() 48 | if err != nil { 49 | pgo.GLogger().Warn("Rabbit ChannelBox.channelClose.channel.Close() err:" + err.Error()) 50 | } 51 | } 52 | } 53 | 54 | func (c *ChannelBox) GetChannel() *amqp.Channel { 55 | return c.channel 56 | } 57 | -------------------------------------------------------------------------------- /Util/Slice.go: -------------------------------------------------------------------------------- 1 | package Util 2 | 3 | // SliceSearch search in a slice has the length of n, 4 | // return the first position where f(i) is true, 5 | // -1 is returned if nothing found. 6 | func SliceSearch(n int, f func(int) bool) int { 7 | for i := 0; i < n; i++ { 8 | if f(i) { 9 | return i 10 | } 11 | } 12 | 13 | return -1 14 | } 15 | 16 | // SliceSearchInt search x in an int slice, return the first position of x, 17 | // -1 is returned if nothing found. 18 | func SliceSearchInt(a []int, x int) int { 19 | return SliceSearch(len(a), func(i int) bool { return a[i] == x }) 20 | } 21 | 22 | // SliceSearchString search x in a string slice, return the first position of x, 23 | // -1 is returned if nothing found. 24 | func SliceSearchString(a []string, x string) int { 25 | return SliceSearch(len(a), func(i int) bool { return a[i] == x }) 26 | } 27 | 28 | // SliceUniqueInt retrieve unique items item from int slice. 29 | func SliceUniqueInt(a []int) []int { 30 | result, exists := make([]int, 0), make(map[int]bool) 31 | for _, v := range a { 32 | if !exists[v] { 33 | result = append(result, v) 34 | exists[v] = true 35 | } 36 | } 37 | 38 | return result 39 | } 40 | 41 | // SliceUniqueString retrieve unique string items from string slice. 42 | func SliceUniqueString(a []string) []string { 43 | result, exists := make([]string, 0), make(map[string]bool) 44 | for _, v := range a { 45 | if !exists[v] { 46 | result = append(result, v) 47 | exists[v] = true 48 | } 49 | } 50 | 51 | return result 52 | } 53 | -------------------------------------------------------------------------------- /File.go: -------------------------------------------------------------------------------- 1 | package pgo 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/pinguo/pgo/Util" 9 | ) 10 | 11 | // File file plugin, this plugin only handle file in @public directory, 12 | // request url with empty or excluded extension will not be handled. 13 | type File struct { 14 | excludeExtensions []string 15 | } 16 | 17 | func (f *File) SetExcludeExtensions(v []interface{}) { 18 | for _, vv := range v { 19 | f.excludeExtensions = append(f.excludeExtensions, vv.(string)) 20 | } 21 | } 22 | 23 | func (f *File) HandleRequest(ctx *Context) { 24 | // if extension is empty or excluded, pass 25 | if ext := filepath.Ext(ctx.GetPath()); ext == "" { 26 | return 27 | } else if len(f.excludeExtensions) != 0 { 28 | if Util.SliceSearchString(f.excludeExtensions, ext) != -1 { 29 | return 30 | } 31 | } 32 | 33 | // skip other plugins 34 | defer ctx.Abort() 35 | 36 | // GET or HEAD method is required 37 | method := ctx.GetMethod() 38 | if method != http.MethodGet && method != http.MethodHead { 39 | http.Error(ctx.GetOutput(), "", http.StatusMethodNotAllowed) 40 | return 41 | } 42 | 43 | // file in @public directory is required 44 | path := filepath.Join(App.GetPublicPath(), Util.CleanPath(ctx.GetPath())) 45 | h, e := os.Open(path) 46 | if e != nil { 47 | http.Error(ctx.GetOutput(), "", http.StatusNotFound) 48 | return 49 | } 50 | 51 | defer h.Close() 52 | info, _ := h.Stat() 53 | http.ServeContent(ctx.GetOutput(), ctx.GetInput(), path, info.ModTime(), h) 54 | } 55 | -------------------------------------------------------------------------------- /Response.go: -------------------------------------------------------------------------------- 1 | package pgo 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | ) 7 | 8 | // Response http.ResponseWriter wrapper 9 | type Response struct { 10 | http.ResponseWriter 11 | status int 12 | size int 13 | } 14 | 15 | func (r *Response) reset(w http.ResponseWriter) { 16 | r.ResponseWriter = w 17 | r.status = http.StatusOK 18 | r.size = -1 19 | } 20 | 21 | func (r *Response) finish() { 22 | if r.size == -1 { 23 | r.size = 0 24 | r.ResponseWriter.WriteHeader(r.status) 25 | } 26 | } 27 | 28 | // WriteHeader cache status code until first write operation. 29 | func (r *Response) WriteHeader(status int) { 30 | if status > 0 && r.status != status && r.size == -1 { 31 | if len(http.StatusText(status)) > 0 { 32 | r.status = status 33 | } 34 | } 35 | } 36 | 37 | // Write write data to underlying http.ResponseWriter 38 | // and record num bytes that has written. 39 | func (r *Response) Write(data []byte) (n int, e error) { 40 | r.finish() 41 | n, e = r.ResponseWriter.Write(data) 42 | r.size += n 43 | return 44 | } 45 | 46 | // WriteString write string data to underlying http.ResponseWriter 47 | // and record num bytes that has written. 48 | func (r *Response) WriteString(s string) (n int, e error) { 49 | r.finish() 50 | n, e = io.WriteString(r.ResponseWriter, s) 51 | r.size += n 52 | return 53 | } 54 | 55 | // ReadFrom is here to optimize copying from a regular file 56 | // to a *net.TCPConn with sendfile. 57 | func (r *Response) ReadFrom(src io.Reader) (n int64, e error) { 58 | r.finish() 59 | if rf, ok := r.ResponseWriter.(io.ReaderFrom); ok { 60 | n, e = rf.ReadFrom(src) 61 | } else { 62 | n, e = io.Copy(r.ResponseWriter, src) 63 | } 64 | r.size += int(n) 65 | return 66 | } 67 | -------------------------------------------------------------------------------- /Util/String.go: -------------------------------------------------------------------------------- 1 | package Util 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | "encoding/hex" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "unicode" 11 | ) 12 | 13 | func IsAllDigit(s string) bool { 14 | for _, v := range s { 15 | if !unicode.IsDigit(v) { 16 | return false 17 | } 18 | } 19 | 20 | if len(s) == 0 { 21 | return false 22 | } 23 | 24 | return true 25 | } 26 | 27 | func IsAllLetter(s string) bool { 28 | for _, v := range s { 29 | if !unicode.IsLetter(v) { 30 | return false 31 | } 32 | } 33 | 34 | if len(s) == 0 { 35 | return false 36 | } 37 | 38 | return true 39 | } 40 | 41 | func IsAllLower(s string) bool { 42 | for _, v := range s { 43 | if !unicode.IsLower(v) { 44 | return false 45 | } 46 | } 47 | 48 | if len(s) == 0 { 49 | return false 50 | } 51 | 52 | return true 53 | } 54 | 55 | func IsAllUpper(s string) bool { 56 | for _, v := range s { 57 | if !unicode.IsUpper(v) { 58 | return false 59 | } 60 | } 61 | 62 | if len(s) == 0 { 63 | return false 64 | } 65 | 66 | return true 67 | } 68 | 69 | // Md5Bytes get 16 bytes of md5 70 | func Md5Bytes(v interface{}) []byte { 71 | ctx := md5.New() 72 | switch vv := v.(type) { 73 | case string: 74 | io.WriteString(ctx, vv) 75 | case []byte: 76 | ctx.Write(vv) 77 | default: 78 | if j, e := json.Marshal(v); e == nil { 79 | ctx.Write(j) 80 | } else { 81 | var buf bytes.Buffer 82 | fmt.Fprint(&buf, v) 83 | ctx.Write(buf.Bytes()) 84 | } 85 | } 86 | 87 | return ctx.Sum(nil) 88 | } 89 | 90 | // Md5String get 32 bytes of md5 hex string 91 | func Md5String(v interface{}) string { 92 | return hex.EncodeToString(Md5Bytes(v)) 93 | } 94 | -------------------------------------------------------------------------------- /gotest.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | ########################################### 3 | # go test # 4 | # for Windows/Mac/Linux # 5 | # # 6 | # Version: 1.0 # 7 | # Author: yangbing@camera360.com # 8 | # Date: 2019/07/05 # 9 | ########################################### 10 | 11 | # 要go test 包名字,如果是mod模式下 module_name/package_name,可修改 12 | arr_test_path_dir_name=( 13 | Command 14 | Controller 15 | Lib 16 | Model 17 | Service 18 | Struct 19 | Test 20 | ) 21 | 22 | go_app_path=`pwd` 23 | exec_mod="default" 24 | 25 | if [[ $# > 0 ]];then 26 | go_app_path=$1 27 | fi 28 | 29 | if [[ $# > 1 ]];then 30 | exec_mod="custom" 31 | fi 32 | 33 | # PgoTestAppBasePath提供给pgo框架使用,必须设置,conf配置文件夹的上一级目录 34 | export PgoTestAppBasePath=$go_app_path 35 | 36 | 37 | exec_default () { 38 | test_path_dir=$go_app_path 39 | cover_dir=${test_path_dir}/coverage 40 | 41 | if [[ ! -d $cover_dir ]];then 42 | mkdir $cover_dir 43 | fi 44 | 45 | if [[ ! -d ${go_app_path}/src ]];then 46 | test_path_dir=${go_app_path}/src 47 | fi 48 | 49 | pkg_str="" 50 | cover_pkg="" 51 | for test_path_name in ${arr_test_path_dir_name[@]} 52 | do 53 | pkg_str=${pkg_str}" "${test_path_name}"/..." 54 | if [[ ${cover_pkg} == "" ]];then 55 | cover_pkg=${test_path_name}"/..." 56 | else 57 | cover_pkg=${cover_pkg}","${test_path_name}"/..." 58 | fi 59 | 60 | done 61 | 62 | go test -coverprofile=${cover_dir}/coverage.data -coverpkg=$cover_pkg $pkg_str 63 | go tool cover -html=${cover_dir}/coverage.data -o ${cover_dir}/coverage.html 64 | go tool cover -func=${cover_dir}/coverage.data -o ${cover_dir}/coverage.txt 65 | } 66 | 67 | case "$exec_mod" in 68 | default) 69 | exec_default 70 | ;; 71 | custom) 72 | ${@:2} 73 | ;; 74 | 75 | esac 76 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # this makefile used in console environment. 2 | # copy this file to project base directory. 3 | 4 | # SET BIN NAME BY USER 5 | binName:=pgo-demo 6 | 7 | goBin:=go 8 | glideBin:=glide 9 | 10 | ######## DO NOT CHANGE THE FLOWING CONTENT ######## 11 | 12 | # absolute path of makefile 13 | mkPath:=$(abspath $(firstword $(MAKEFILE_LIST))) 14 | 15 | # absolute base directory of project 16 | baseDir:=$(strip $(patsubst %/, %, $(dir $(mkPath)))) 17 | 18 | binDir:=$(baseDir)/bin 19 | srcDir:=$(baseDir)/src 20 | 21 | .PHONY: start stop build update pgo init 22 | 23 | start: build 24 | $(binDir)/$(binName) 25 | 26 | stop: 27 | -killall $(binName) 28 | 29 | build: 30 | export GOPATH=$(baseDir) && $(goBin) build -o $(binDir)/$(binName) $(srcDir)/Main/main.go 31 | 32 | update: 33 | export GOPATH=$(baseDir) && cd $(srcDir) && $(glideBin) update 34 | 35 | install: 36 | export GOPATH=$(baseDir) && cd $(srcDir) && $(glideBin) install 37 | 38 | pgo: 39 | export GOPATH=$(baseDir) && cd $(srcDir) && $(glideBin) get github.com/pinguo/pgo 40 | 41 | init: 42 | [ -d $(baseDir)/conf ] || mkdir $(baseDir)/conf 43 | [ -d $(srcDir) ] || mkdir $(srcDir) 44 | [ -d $(srcDir)/Command ] || mkdir $(srcDir)/Command 45 | [ -d $(srcDir)/Controller ] || mkdir $(srcDir)/Controller 46 | [ -d $(srcDir)/Lib ] || mkdir $(srcDir)/Lib 47 | [ -d $(srcDir)/Main ] || mkdir $(srcDir)/Main 48 | [ -d $(srcDir)/Model ] || mkdir $(srcDir)/Model 49 | [ -d $(srcDir)/Service ] || mkdir $(srcDir)/Service 50 | [ -d $(srcDir)/Struct ] || mkdir $(srcDir)/Struct 51 | [ -d $(srcDir)/Test ] || mkdir $(srcDir)/Test 52 | [ -f $(srcDir)/glide.yaml ] || (cd $(srcDir) && echo Y | $(glideBin) init) 53 | 54 | help: 55 | @echo "make start build and start $(binName)" 56 | @echo "make stop stop process $(binName)" 57 | @echo "make build build $(binName)" 58 | @echo "make update glide update packages recursively" 59 | @echo "make install glide install packages in glide.lock" 60 | @echo "make pgo glide get pgo" 61 | @echo "make init init project" 62 | -------------------------------------------------------------------------------- /Client/Memcache/Init.go: -------------------------------------------------------------------------------- 1 | package Memcache 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/pinguo/pgo" 7 | ) 8 | 9 | const ( 10 | AdapterClass = "@pgo/Client/Memcache/Adapter" 11 | 12 | defaultComponentId = "memcache" 13 | defaultServer = "127.0.0.1:11211" 14 | defaultPrefix = "pgo_" 15 | defaultProbe = 0 16 | defaultIdleConn = 10 17 | defaultIdleTime = 60 * time.Second 18 | defaultTimeout = 1 * time.Second 19 | defaultExpire = 24 * time.Hour 20 | 21 | maxProbeInterval = 30 * time.Second 22 | minProbeInterval = 1 * time.Second 23 | 24 | CmdCas = "cas" 25 | CmdAdd = "add" 26 | CmdSet = "set" 27 | CmdReplace = "replace" 28 | CmdAppend = "append" 29 | CmdPrepend = "prepend" 30 | CmdGet = "get" 31 | CmdGets = "gets" 32 | CmdDelete = "delete" 33 | CmdIncr = "incr" 34 | CmdDecr = "decr" 35 | CmdTouch = "touch" 36 | CmdStats = "stats" 37 | CmdFlushAll = "flush_all" 38 | CmdVersion = "version" 39 | 40 | errBase = "memcache: " 41 | errSetProp = "memcache: failed to set %s, %s" 42 | errNoServer = "memcache: no server available" 43 | errInvalidCmd = "memcache: invalid cmd, " 44 | errSendFailed = "memcache: send request failed, " 45 | errReadFailed = "memcache: read response failed, " 46 | errEmptyKeys = "memcache: empty keys" 47 | errCorrupted = "memcache: corrupted response, " 48 | ) 49 | 50 | var ( 51 | lineEnding = []byte("\r\n") 52 | replyOK = []byte("OK\r\n") 53 | replyStored = []byte("STORED\r\n") 54 | replyNotStored = []byte("NOT_STORED\r\n") 55 | replyExists = []byte("EXISTS\r\n") 56 | replyNotFound = []byte("NOT_FOUND\r\n") 57 | replyDeleted = []byte("DELETED\r\n") 58 | replyTouched = []byte("TOUCHED\r\n") 59 | replyEnd = []byte("END\r\n") 60 | ) 61 | 62 | func init() { 63 | container := pgo.App.GetContainer() 64 | 65 | container.Bind(&Adapter{}) 66 | container.Bind(&Client{}) 67 | } 68 | -------------------------------------------------------------------------------- /Router.go: -------------------------------------------------------------------------------- 1 | package pgo 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | 7 | "github.com/pinguo/pgo/Util" 8 | ) 9 | 10 | // format route string to CamelCase, eg. 11 | // /api/foo-bar/say-hello => /Api/FooBar/SayHello 12 | func routeFormatFunc(s string) string { 13 | s = strings.ToUpper(s) 14 | if s[0] == '-' { 15 | s = s[1:] 16 | } 17 | return s 18 | } 19 | 20 | type routeRule struct { 21 | rePat *regexp.Regexp 22 | pattern string 23 | route string 24 | } 25 | 26 | // Router the router component, configuration: 27 | // router: 28 | // rules: 29 | // - "^/foo/all$ => /foo/index" 30 | // - "^/api/user/(\d+)$ => /api/user" 31 | type Router struct { 32 | reFmt *regexp.Regexp 33 | rules []routeRule 34 | } 35 | 36 | func (r *Router) Construct() { 37 | r.reFmt = regexp.MustCompile(`([/-][a-z])`) 38 | r.rules = make([]routeRule, 0, 10) 39 | } 40 | 41 | // SetRules set rule list, format: `^/api/user/(\d+)$ => /api/user` 42 | func (r *Router) SetRules(rules []interface{}) { 43 | for _, v := range rules { 44 | parts := strings.Split(v.(string), "=>") 45 | if len(parts) != 2 { 46 | panic("Router: invalid rule: " + Util.ToString(v)) 47 | } 48 | 49 | pattern := strings.TrimSpace(parts[0]) 50 | route := strings.TrimSpace(parts[1]) 51 | r.AddRoute(pattern, route) 52 | } 53 | } 54 | 55 | // AddRoute add one route, the captured group will be passed to 56 | // action method as function params 57 | func (r *Router) AddRoute(pattern, route string) { 58 | rePat := regexp.MustCompile(pattern) 59 | rule := routeRule{rePat, pattern, route} 60 | r.rules = append(r.rules, rule) 61 | } 62 | 63 | // Resolve path to route and action params, then format route to CamelCase 64 | func (r *Router) Resolve(path string) (route string, params []string) { 65 | path = Util.CleanPath(path) 66 | 67 | if len(r.rules) != 0 { 68 | for _, rule := range r.rules { 69 | matches := rule.rePat.FindStringSubmatch(path) 70 | if len(matches) != 0 { 71 | path = rule.route 72 | params = matches[1:] 73 | break 74 | } 75 | } 76 | } 77 | 78 | route = r.reFmt.ReplaceAllStringFunc(path, routeFormatFunc) 79 | return 80 | } 81 | -------------------------------------------------------------------------------- /Gzip.go: -------------------------------------------------------------------------------- 1 | package pgo 2 | 3 | import ( 4 | "compress/gzip" 5 | "io" 6 | "io/ioutil" 7 | "net/http" 8 | "path/filepath" 9 | "strings" 10 | "sync" 11 | ) 12 | 13 | // Gzip gzip compression plugin 14 | type Gzip struct { 15 | pool sync.Pool 16 | } 17 | 18 | func (g *Gzip) Construct() { 19 | g.pool.New = func() interface{} { 20 | return &gzipWriter{writer: gzip.NewWriter(ioutil.Discard)} 21 | } 22 | } 23 | 24 | func (g *Gzip) HandleRequest(ctx *Context) { 25 | ae := ctx.GetHeader("Accept-Encoding", "") 26 | if !strings.Contains(ae, "gzip") { 27 | return 28 | } 29 | 30 | ext := filepath.Ext(ctx.GetPath()) 31 | switch strings.ToLower(ext) { 32 | case ".png", ".gif", ".jpeg", ".jpg", ".ico": 33 | return 34 | } 35 | 36 | gw := g.pool.Get().(*gzipWriter) 37 | gw.reset(ctx) 38 | 39 | defer func() { 40 | gw.finish() 41 | g.pool.Put(gw) 42 | }() 43 | 44 | ctx.Next() 45 | } 46 | 47 | type gzipWriter struct { 48 | http.ResponseWriter 49 | writer *gzip.Writer 50 | ctx *Context 51 | size int 52 | } 53 | 54 | func (g *gzipWriter) reset(ctx *Context) { 55 | g.ResponseWriter = ctx.GetOutput() 56 | g.ctx = ctx 57 | g.size = -1 58 | ctx.SetOutput(g) 59 | } 60 | 61 | func (g *gzipWriter) finish() { 62 | if g.size > 0 { 63 | g.writer.Close() 64 | } 65 | } 66 | 67 | func (g *gzipWriter) start() { 68 | if g.size == -1 { 69 | g.size = 0 70 | g.writer.Reset(g.ResponseWriter) 71 | g.ctx.SetHeader("Content-Encoding", "gzip") 72 | } 73 | } 74 | 75 | func (g *gzipWriter) Flush() { 76 | if g.size > 0 { 77 | g.writer.Flush() 78 | } 79 | 80 | if flusher, ok := g.ResponseWriter.(http.Flusher); ok { 81 | flusher.Flush() 82 | } 83 | } 84 | 85 | func (g *gzipWriter) Write(data []byte) (n int, e error) { 86 | if len(data) == 0 { 87 | return 0, nil 88 | } 89 | 90 | g.start() 91 | 92 | n, e = g.writer.Write(data) 93 | g.size += n 94 | return 95 | } 96 | 97 | func (g *gzipWriter) WriteString(data string) (n int, e error) { 98 | if len(data) == 0 { 99 | return 0, nil 100 | } 101 | 102 | g.start() 103 | 104 | n, e = io.WriteString(g.writer, data) 105 | g.size += n 106 | return 107 | } 108 | -------------------------------------------------------------------------------- /Util/Map.go: -------------------------------------------------------------------------------- 1 | package Util 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // MapClear clear map to empty 9 | func MapClear(m map[string]interface{}) { 10 | for k := range m { 11 | delete(m, k) 12 | } 13 | } 14 | 15 | // MapMerge merge map recursively 16 | func MapMerge(a map[string]interface{}, m ...map[string]interface{}) { 17 | for _, b := range m { 18 | for k := range b { 19 | va, oa := a[k].(map[string]interface{}) 20 | vb, ob := b[k].(map[string]interface{}) 21 | 22 | if oa && ob { 23 | MapMerge(va, vb) 24 | } else { 25 | a[k] = b[k] 26 | } 27 | } 28 | } 29 | } 30 | 31 | // MapGet get value by dot separated key, empty key for m itself 32 | func MapGet(m map[string]interface{}, key string) interface{} { 33 | var data interface{} = m 34 | ks := strings.Split(key, ".") 35 | 36 | for _, k := range ks { 37 | // skip empty key segment 38 | if k = strings.TrimSpace(k); len(k) == 0 { 39 | continue 40 | } 41 | 42 | if v, ok := data.(map[string]interface{}); ok { 43 | if data, ok = v[k]; ok { 44 | continue 45 | } 46 | } 47 | 48 | // not found 49 | return nil 50 | } 51 | 52 | return data 53 | } 54 | 55 | // MapSet set value by dot separated key, empty key for root, nil val for clear 56 | func MapSet(m map[string]interface{}, key string, val interface{}) { 57 | data := m 58 | last := "" 59 | 60 | ks := strings.Split(key, ".") 61 | for _, k := range ks { 62 | // skip empty key segment 63 | if k = strings.TrimSpace(k); len(k) == 0 { 64 | continue 65 | } 66 | 67 | if len(last) > 0 { 68 | if _, ok := data[last].(map[string]interface{}); !ok { 69 | data[last] = make(map[string]interface{}) 70 | } 71 | 72 | data = data[last].(map[string]interface{}) 73 | } 74 | 75 | last = k 76 | } 77 | 78 | if len(last) > 0 { 79 | if nil == val { 80 | delete(data, last) 81 | } else { 82 | data[last] = val 83 | } 84 | } else { 85 | MapClear(m) 86 | if v, ok := val.(map[string]interface{}); ok { 87 | MapMerge(m, v) 88 | } else if nil != val { 89 | panic(fmt.Sprintf("MapSet: invalid type: %T", val)) 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /I18n.go: -------------------------------------------------------------------------------- 1 | package pgo 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/pinguo/pgo/Util" 8 | ) 9 | 10 | // I18n the internationalization component, 11 | // language format is ll-CC or ll, 12 | // lower case lang code, upper case area code. 13 | // lang file name format is i18n_{lang}.json, 14 | // and located in conf directory, configuration: 15 | // i18n: 16 | // sourceLang: "en" 17 | // targetLang: [ "en", "zh-CN", "zh-TW"] 18 | type I18n struct { 19 | sourceLang string 20 | targetLang map[string]bool 21 | } 22 | 23 | func (i *I18n) Construct() { 24 | i.sourceLang = "en" 25 | i.targetLang = make(map[string]bool) 26 | } 27 | 28 | // SetSourceLang set language of source 29 | func (i *I18n) SetSourceLang(lang string) { 30 | i.sourceLang = lang 31 | } 32 | 33 | // SetTargetLang set language of target 34 | func (i *I18n) SetTargetLang(targets []interface{}) { 35 | for _, v := range targets { 36 | lang := Util.ToString(v) 37 | i.targetLang[lang] = true 38 | } 39 | } 40 | 41 | // Translate message to target lang, lang format is one of the following: 42 | // 1. accept-language header value: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7 43 | // 2. ll-CC: lower case lang code and upper case area code, zh-CN 44 | // 3. ll: lower case of lang code without area code, zh 45 | func (i *I18n) Translate(message, lang string, params ...interface{}) string { 46 | translation := i.loadMessage(message, i.detectLang(lang)) 47 | if len(params) > 0 { 48 | return fmt.Sprintf(translation, params...) 49 | } 50 | 51 | return translation 52 | } 53 | 54 | // detect support lang, lang can be accept-language header 55 | func (i *I18n) detectLang(lang string) string { 56 | // use first part of accept-language 57 | if pos := strings.IndexByte(lang, ','); pos > 0 { 58 | lang = lang[:pos] 59 | } 60 | 61 | // omit q weight 62 | if pos := strings.IndexByte(lang, ';'); pos > 0 { 63 | lang = lang[:pos] 64 | } 65 | 66 | // format lang to ll-CC format 67 | lang = Util.FormatLanguage(lang) 68 | 69 | if i.targetLang[lang] { 70 | return lang 71 | } 72 | 73 | if pos := strings.IndexByte(lang, '-'); pos > 0 { 74 | if lang = lang[:pos]; i.targetLang[lang] { 75 | return lang 76 | } 77 | } 78 | 79 | return i.sourceLang 80 | } 81 | 82 | // load message from lang file i18n_{lang}.json 83 | func (i *I18n) loadMessage(message, lang string) string { 84 | if !i.targetLang[lang] { 85 | return message 86 | } 87 | 88 | key := fmt.Sprintf("i18n_%s.%s", lang, message) 89 | return App.GetConfig().GetString(key, message) 90 | } 91 | -------------------------------------------------------------------------------- /Client/RabbitMq/Adapter.go: -------------------------------------------------------------------------------- 1 | package RabbitMq 2 | 3 | import ( 4 | "github.com/pinguo/pgo" 5 | "github.com/pinguo/pgo/Util" 6 | "github.com/streadway/amqp" 7 | ) 8 | 9 | type Adapter struct { 10 | pgo.Object 11 | client *Client 12 | panicRecover bool 13 | } 14 | 15 | func (a *Adapter) Construct(componentId ...string) { 16 | id := defaultComponentId 17 | if len(componentId) > 0 { 18 | id = componentId[0] 19 | } 20 | 21 | a.client = pgo.App.Get(id).(*Client) 22 | a.panicRecover = true 23 | } 24 | 25 | func (a *Adapter) SetPanicRecover(v bool) { 26 | a.panicRecover = v 27 | } 28 | 29 | func (a *Adapter) GetClient() *Client { 30 | return a.client 31 | } 32 | 33 | func (a *Adapter) handlePanic() { 34 | if a.panicRecover { 35 | if v := recover(); v != nil { 36 | a.GetContext().Error(Util.ToString(v)) 37 | } 38 | } 39 | } 40 | 41 | func (a *Adapter) ExchangeDeclare() { 42 | profile := "rabbit.ExchangeDeclare" 43 | a.GetContext().ProfileStart(profile) 44 | defer a.GetContext().ProfileStop(profile) 45 | defer a.handlePanic() 46 | a.client.setExchangeDeclare() 47 | } 48 | 49 | func (a *Adapter) Publish(opCode string, data interface{}, dftOpUid ...string) bool { 50 | profile := "rabbit.Publish" 51 | a.GetContext().ProfileStart(profile) 52 | defer a.GetContext().ProfileStop(profile) 53 | defer a.handlePanic() 54 | 55 | opUid := "" 56 | if len(dftOpUid) > 0 { 57 | opUid = dftOpUid[0] 58 | } 59 | 60 | return a.client.publish(&PublishData{OpCode: opCode, Data: data, OpUid: opUid}, a.GetContext().GetLogId()) 61 | } 62 | 63 | func (a *Adapter) GetConsumeChannelBox(queueName string, opCodes []string) *ChannelBox { 64 | profile := "rabbit.GetConsumeChannelBox" 65 | a.GetContext().ProfileStart(profile) 66 | defer a.GetContext().ProfileStop(profile) 67 | defer a.handlePanic() 68 | 69 | return a.client.getConsumeChannelBox(queueName, opCodes) 70 | } 71 | 72 | // 消费,返回chan 可以不停取数据 73 | // queueName 队列名字 74 | // opCodes 绑定队列的code 75 | // limit 每次接收多少条 76 | // autoAck 是否自动答复 如果为false 需要手动调用Delivery.ack(false) 77 | // noWait 是否一直等待 78 | // 是否独占队列 79 | func (a *Adapter) Consume(queueName string, opCodes []string, limit int, autoAck, noWait, exclusive bool) <-chan amqp.Delivery { 80 | profile := "rabbit.Consume" 81 | a.GetContext().ProfileStart(profile) 82 | defer a.GetContext().ProfileStop(profile) 83 | defer a.handlePanic() 84 | 85 | return a.client.consume(&ConsumeData{QueueName: queueName, OpCodes: opCodes, Limit: limit, AutoAck: autoAck, NoWait: noWait, Exclusive: exclusive}) 86 | } 87 | 88 | func (a *Adapter) DecodeBody(d amqp.Delivery, ret interface{}) error { 89 | return a.client.decodeBody(d,ret) 90 | } 91 | 92 | func (a *Adapter) DecodeHeaders(d amqp.Delivery) *RabbitHeaders { 93 | return a.client.decodeHeaders(d) 94 | } -------------------------------------------------------------------------------- /Util/Struct.go: -------------------------------------------------------------------------------- 1 | package Util 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // STMergeSame merge none zero field of s2 into s1, 8 | // s1 and s2 must have the same struct type. 9 | // unexported field will be skipped and embedded struct 10 | // field will be treated as a single field. 11 | func STMergeSame(s1, s2 interface{}) { 12 | v1 := reflect.ValueOf(s1) 13 | if v1.Kind() == reflect.Ptr { 14 | v1 = v1.Elem() 15 | } else { 16 | panic("STMergeSame: param 1 must be pointer") 17 | } 18 | 19 | v2 := reflect.ValueOf(s2) 20 | if v2.Kind() == reflect.Ptr { 21 | v2 = v2.Elem() 22 | } 23 | 24 | if v1.Kind() != reflect.Struct || v2.Kind() != reflect.Struct { 25 | panic("STMergeSame: both params must be struct") 26 | } 27 | 28 | if v1.Type() != v2.Type() { 29 | panic("STMergeSame: params must have same type") 30 | } 31 | 32 | for i, n := 0, v2.NumField(); i < n; i++ { 33 | field1, field2 := v1.Field(i), v2.Field(i) 34 | zero := reflect.Zero(field2.Type()) 35 | 36 | // merge none zero field, unexported field will be skipped. 37 | // use reflect.DeepEqual to check equality of underlying value, 38 | // because slice type does not support equal operator. 39 | if field2.CanInterface() && !reflect.DeepEqual(field2.Interface(), zero.Interface()) { 40 | if field1.CanSet() { 41 | field1.Set(field2) 42 | } 43 | } 44 | } 45 | } 46 | 47 | // STMergeField merge the same or compatible field of s2 into s1, 48 | // zero and unexported field will be skipped and embedded struct 49 | // field will be treated as a single field. 50 | func STMergeField(s1, s2 interface{}) { 51 | v1 := reflect.ValueOf(s1) 52 | if v1.Kind() == reflect.Ptr { 53 | v1 = v1.Elem() 54 | } else { 55 | panic("STMergeField: param 1 must be pointer") 56 | } 57 | 58 | v2 := reflect.ValueOf(s2) 59 | if v2.Kind() == reflect.Ptr { 60 | v2 = v2.Elem() 61 | } 62 | 63 | if v1.Kind() != reflect.Struct || v2.Kind() != reflect.Struct { 64 | panic("STMergeField: both params must be struct") 65 | } 66 | 67 | for i, n := 0, v2.NumField(); i < n; i++ { 68 | field2 := v2.Field(i) 69 | zero := reflect.Zero(field2.Type()) 70 | 71 | // skip zero or unexported field, see STMergeSame(s1, s2) 72 | if !field2.CanInterface() || reflect.DeepEqual(field2.Interface(), zero.Interface()) { 73 | continue 74 | } 75 | 76 | // get field1 by name of field2 77 | field1 := v1.FieldByName(v2.Type().Field(i).Name) 78 | if !field1.IsValid() || !field1.CanSet() { 79 | continue 80 | } 81 | 82 | // check if type of two field is convertible 83 | if !field2.Type().ConvertibleTo(field1.Type()) { 84 | continue 85 | } 86 | 87 | // set value of field1 to convertible value of field2 88 | field1.Set(field2.Convert(field1.Type())) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Client/Mongo/Client.go: -------------------------------------------------------------------------------- 1 | package Mongo 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strings" 7 | "time" 8 | 9 | "github.com/globalsign/mgo" 10 | ) 11 | 12 | // Mongo Client component, configuration: 13 | // mongo: 14 | // class: "@pgo/Client/Mongo/Client" 15 | // dsn: "mongodb://host1:port1/[db][?options]" 16 | // connectTimeout: "1s" 17 | // readTimeout: "10s" 18 | // writeTimeout: "10s" 19 | // 20 | // see Dial() for query options, default: 21 | // replicaSet= 22 | // connect=replicaSet 23 | // maxPoolSize=100 24 | // minPoolSize=1 25 | // maxIdleTimeMS=300000 26 | // ssl=false 27 | // w=1 28 | // j=false 29 | // wtimeoutMS=10000 30 | // readPreference=secondaryPreferred 31 | type Client struct { 32 | session *mgo.Session 33 | dsn string 34 | connectTimeout time.Duration 35 | readTimeout time.Duration 36 | writeTimeout time.Duration 37 | } 38 | 39 | func (c *Client) Construct() { 40 | c.dsn = defaultDsn 41 | c.connectTimeout = defaultConnectTimeout 42 | c.readTimeout = defaultReadTimeout 43 | c.writeTimeout = defaultWriteTimeout 44 | } 45 | 46 | func (c *Client) Init() { 47 | server, query := c.dsn, defaultOptions 48 | if pos := strings.IndexByte(c.dsn, '?'); pos > 0 { 49 | dsnOpts, _ := url.ParseQuery(c.dsn[pos+1:]) 50 | options, _ := url.ParseQuery(defaultOptions) 51 | 52 | for k, v := range dsnOpts { 53 | if len(v) > 0 && len(v[0]) > 0 { 54 | options.Set(k, v[0]) 55 | } 56 | } 57 | server = c.dsn[:pos] 58 | query = options.Encode() 59 | } 60 | 61 | c.dsn = server + "?" + query 62 | dialInfo, e := mgo.ParseURL(c.dsn) 63 | if e != nil { 64 | panic(fmt.Sprintf(errInvalidDsn, c.dsn, e.Error())) 65 | } 66 | 67 | dialInfo.Timeout = c.connectTimeout 68 | dialInfo.ReadTimeout = c.readTimeout 69 | dialInfo.WriteTimeout = c.writeTimeout 70 | 71 | if c.session, e = mgo.DialWithInfo(dialInfo); e != nil { 72 | panic(fmt.Sprintf(errDialFailed, c.dsn, e.Error())) 73 | } 74 | 75 | c.session.SetMode(mgo.Monotonic, true) 76 | } 77 | 78 | func (c *Client) SetDsn(dsn string) { 79 | c.dsn = dsn 80 | } 81 | 82 | func (c *Client) SetConnectTimeout(v string) { 83 | if connectTimeout, e := time.ParseDuration(v); e != nil { 84 | panic(fmt.Sprintf(errSetProp, "connectTimeout", e.Error())) 85 | } else { 86 | c.connectTimeout = connectTimeout 87 | } 88 | } 89 | 90 | func (c *Client) SetReadTimeout(v string) { 91 | if readTimeout, e := time.ParseDuration(v); e != nil { 92 | panic(fmt.Sprintf(errSetProp, "readTimeout", e.Error())) 93 | } else { 94 | c.readTimeout = readTimeout 95 | } 96 | } 97 | 98 | func (c *Client) SetWriteTimeout(v string) { 99 | if writeTimeout, e := time.ParseDuration(v); e != nil { 100 | panic(fmt.Sprintf(errSetProp, "writeTimeout", e.Error())) 101 | } else { 102 | c.writeTimeout = writeTimeout 103 | } 104 | } 105 | 106 | func (c *Client) GetSession() *mgo.Session { 107 | return c.session.Copy() 108 | } 109 | -------------------------------------------------------------------------------- /Util/Yaml.go: -------------------------------------------------------------------------------- 1 | package Util 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/go-yaml/yaml" 8 | ) 9 | 10 | // YamlMarshal wrapper for yaml.Marshal. 11 | func YamlMarshal(in interface{}) ([]byte, error) { 12 | return yaml.Marshal(in) 13 | } 14 | 15 | // YamlUnmarshal wrapper for yaml.Unmarshal or yaml.UnmarshalStrict, 16 | // if type of out is map[string]interface{}, *map[string]interface{}, 17 | // the inner map[interface{}]interface{} will fix to map[string]interface{} 18 | // recursively. if type of out is *interface{}, the underlying type of 19 | // out will change to *map[string]interface{}. 20 | func YamlUnmarshal(in []byte, out interface{}, strict ...bool) error { 21 | var err error 22 | if len(strict) > 0 && strict[0] { 23 | err = yaml.UnmarshalStrict(in, out) 24 | } else { 25 | err = yaml.Unmarshal(in, out) 26 | } 27 | 28 | if err == nil { 29 | yamlFixOut(out) 30 | } 31 | 32 | return err 33 | } 34 | 35 | // YamlEncode wrapper for yaml.Encoder. 36 | func YamlEncode(w io.Writer, in interface{}) error { 37 | enc := yaml.NewEncoder(w) 38 | defer enc.Close() 39 | return enc.Encode(in) 40 | } 41 | 42 | // YamlDecode wrapper for yaml.Decoder, strict is for Decoder.SetStrict(). 43 | // if type of out is map[string]interface{}, *map[string]interface{}, 44 | // the inner map[interface{}]interface{} will fix to map[string]interface{} 45 | // recursively. if type of out is *interface{}, the underlying type of 46 | // out will change to *map[string]interface{}. 47 | func YamlDecode(r io.Reader, out interface{}, strict ...bool) error { 48 | dec := yaml.NewDecoder(r) 49 | if len(strict) > 0 && strict[0] { 50 | dec.SetStrict(true) 51 | } 52 | 53 | err := dec.Decode(out) 54 | if err == nil { 55 | yamlFixOut(out) 56 | } 57 | 58 | return err 59 | } 60 | 61 | func yamlFixOut(out interface{}) { 62 | switch v := out.(type) { 63 | case *map[string]interface{}: 64 | for key, val := range *v { 65 | (*v)[key] = yamlCleanValue(val) 66 | } 67 | 68 | case map[string]interface{}: 69 | for key, val := range v { 70 | v[key] = yamlCleanValue(val) 71 | } 72 | 73 | case *interface{}: 74 | if vv, ok := (*v).(map[interface{}]interface{}); ok { 75 | *v = yamlCleanMap(vv) 76 | } 77 | } 78 | } 79 | 80 | func yamlCleanValue(v interface{}) interface{} { 81 | switch vv := v.(type) { 82 | case map[interface{}]interface{}: 83 | return yamlCleanMap(vv) 84 | 85 | case []interface{}: 86 | return yamlCleanArray(vv) 87 | 88 | default: 89 | return v 90 | } 91 | } 92 | 93 | func yamlCleanMap(in map[interface{}]interface{}) map[string]interface{} { 94 | result := make(map[string]interface{}, len(in)) 95 | for k, v := range in { 96 | result[fmt.Sprintf("%v", k)] = yamlCleanValue(v) 97 | } 98 | return result 99 | } 100 | 101 | func yamlCleanArray(in []interface{}) []interface{} { 102 | result := make([]interface{}, len(in)) 103 | for k, v := range in { 104 | result[k] = yamlCleanValue(v) 105 | } 106 | return result 107 | } 108 | -------------------------------------------------------------------------------- /View.go: -------------------------------------------------------------------------------- 1 | package pgo 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "html/template" 7 | "io" 8 | "path/filepath" 9 | "sync" 10 | ) 11 | 12 | // View the view component, configuration: 13 | // view: 14 | // suffix: ".html" 15 | // commons: 16 | // - "@view/common/header.html" 17 | // - "@view/common/footer.html" 18 | type View struct { 19 | suffix string 20 | commons []string 21 | funcMap template.FuncMap 22 | templates map[string]*template.Template 23 | lock sync.RWMutex 24 | } 25 | 26 | func (v *View) Construct() { 27 | v.suffix = ".html" 28 | v.commons = make([]string, 0) 29 | v.templates = make(map[string]*template.Template) 30 | } 31 | 32 | // SetSuffix set view file suffix, default is ".html" 33 | func (v *View) SetSuffix(suffix string) { 34 | if len(suffix) > 0 && suffix[0] != '.' { 35 | suffix = "." + suffix 36 | } 37 | 38 | if len(suffix) > 1 { 39 | v.suffix = suffix 40 | } 41 | } 42 | 43 | // SetCommons set common view files 44 | func (v *View) SetCommons(commons []interface{}) { 45 | for _, p := range commons { 46 | if view, ok := p.(string); ok { 47 | v.commons = append(v.commons, v.normalize(view)) 48 | } else { 49 | panic(fmt.Sprintf("invalid common view, %s", p)) 50 | } 51 | } 52 | } 53 | 54 | // AddFuncMap add custom func map 55 | func (v *View) AddFuncMap(funcMap template.FuncMap) { 56 | v.funcMap = funcMap 57 | } 58 | 59 | // Render render view and return result 60 | func (v *View) Render(view string, data interface{}) []byte { 61 | buf := &bytes.Buffer{} 62 | v.Display(buf, view, data) 63 | return buf.Bytes() 64 | } 65 | 66 | // Display render view and display result 67 | func (v *View) Display(w io.Writer, view string, data interface{}) { 68 | view = v.normalize(view) 69 | tpl := v.getTemplate(view) 70 | e := tpl.Execute(w, data) 71 | if e != nil { 72 | panic(fmt.Sprintf("failed to render view, %s, %s", view, e)) 73 | } 74 | } 75 | 76 | func (v *View) getTemplate(view string) *template.Template { 77 | if _, ok := v.templates[view]; !ok { 78 | v.loadTemplate(view) 79 | } 80 | 81 | v.lock.RLock() 82 | defer v.lock.RUnlock() 83 | 84 | return v.templates[view] 85 | } 86 | 87 | func (v *View) loadTemplate(view string) { 88 | v.lock.Lock() 89 | defer v.lock.Unlock() 90 | 91 | // avoid repeated loading 92 | if _, ok := v.templates[view]; ok { 93 | return 94 | } 95 | 96 | files := []string{view} 97 | if len(v.commons) > 0 { 98 | files = append(files, v.commons...) 99 | } 100 | 101 | tpl := template.New(filepath.Base(view)) 102 | 103 | // add custom func map 104 | if len(v.funcMap) > 0 { 105 | tpl.Funcs(v.funcMap) 106 | } 107 | 108 | // parse template files 109 | _, e := tpl.ParseFiles(files...) 110 | if e != nil { 111 | panic(fmt.Sprintf("failed to parse template, %s, %s", view, e)) 112 | } 113 | 114 | v.templates[view] = tpl 115 | } 116 | 117 | func (v *View) normalize(view string) string { 118 | if ext := filepath.Ext(view); len(ext) == 0 { 119 | view = view + v.suffix 120 | } 121 | 122 | if view[:5] != "@view" { 123 | view = "@view/" + view 124 | } 125 | 126 | return GetAlias(view) 127 | } 128 | -------------------------------------------------------------------------------- /Util/Hash.go: -------------------------------------------------------------------------------- 1 | package Util 2 | 3 | import ( 4 | "crypto/sha1" 5 | "fmt" 6 | "hash/crc32" 7 | "io" 8 | "math" 9 | "sort" 10 | ) 11 | 12 | func HashSha1Crc32(s string) uint32 { 13 | sh := sha1.New() 14 | io.WriteString(sh, s) 15 | return crc32.ChecksumIEEE(sh.Sum(nil)) 16 | } 17 | 18 | // new hash ring, optional args: 19 | // node string: node item key, multi node accepted, see AddNode(). 20 | // spots int: num of virtual spots for each node, default 32. 21 | // hashFunc HashFunc: function to calculate hash value, default HashSha1Crc32. 22 | // eg. h := NewHashRing("127.0.0.1:6379", "127.0.0.1:6379", "127.0.0.1:6380") 23 | func NewHashRing(args ...interface{}) *HashRing { 24 | h := &HashRing{ 25 | numSpots: 32, 26 | hashFunc: HashSha1Crc32, 27 | weights: make(map[string]int), 28 | items: make([]hashItem, 0), 29 | } 30 | 31 | for _, arg := range args { 32 | switch v := arg.(type) { 33 | case string: 34 | if len(v) > 0 { 35 | h.AddNode(v) 36 | } 37 | case int: 38 | if v > 0 { 39 | h.numSpots = v 40 | } 41 | case HashFunc: 42 | if v != nil { 43 | h.hashFunc = v 44 | } 45 | } 46 | } 47 | 48 | return h 49 | } 50 | 51 | type HashRing struct { 52 | numSpots int 53 | hashFunc HashFunc 54 | weights map[string]int 55 | items []hashItem 56 | } 57 | 58 | type hashItem struct { 59 | node string 60 | value uint32 61 | } 62 | 63 | type HashFunc func(string) uint32 64 | 65 | // add node to hash ring with default 1 weight, 66 | // if node add multiple times, it gets 67 | // a proportional amount of weight. 68 | func (h *HashRing) AddNode(node string, w ...int) { 69 | weight := h.weights[node] 70 | if len(w) == 1 && w[0] > 0 { 71 | weight += w[0] 72 | } else { 73 | weight += 1 74 | } 75 | 76 | h.weights[node] = weight 77 | h.init() 78 | } 79 | 80 | // remove node from hash ring 81 | func (h *HashRing) DelNode(node string) { 82 | delete(h.weights, node) 83 | h.init() 84 | } 85 | 86 | // get node from hash ring by the hash value of key 87 | func (h *HashRing) GetNode(key string) string { 88 | if len(h.weights) == 0 { 89 | return "" 90 | } 91 | 92 | if len(h.weights) == 1 { 93 | return h.items[0].node 94 | } 95 | 96 | hl := len(h.items) 97 | hv := h.hashFunc(key) 98 | iv := sort.Search(hl, func(i int) bool { 99 | return h.items[i].value >= hv 100 | }) 101 | 102 | return h.items[iv%hl].node 103 | } 104 | 105 | func (h *HashRing) init() { 106 | totalWeight := 0 107 | for _, w := range h.weights { 108 | totalWeight += w 109 | } 110 | 111 | totalSpots := h.numSpots * len(h.weights) 112 | h.items = h.items[:0] 113 | 114 | for n, w := range h.weights { 115 | spots := int(math.Round(float64(totalSpots) * float64(w) / float64(totalWeight))) 116 | if spots <= 0 { 117 | spots = 1 118 | } 119 | 120 | for i := 0; i < spots; i++ { 121 | v := h.hashFunc(fmt.Sprintf("%s:%d", n, i)) 122 | h.items = append(h.items, hashItem{n, v}) 123 | } 124 | } 125 | 126 | sort.Slice(h.items, func(i, j int) bool { return h.items[i].value < h.items[j].value }) 127 | } 128 | -------------------------------------------------------------------------------- /Client/Http/Adapter.go: -------------------------------------------------------------------------------- 1 | package Http 2 | 3 | import ( 4 | "net/http" 5 | "sync" 6 | "time" 7 | 8 | "github.com/pinguo/pgo" 9 | "github.com/pinguo/pgo/Util" 10 | ) 11 | 12 | // Adapter of Http Client, add context support. 13 | // usage: http := this.GetObject(Http.AdapterClass).(*Http.Adapter) 14 | type Adapter struct { 15 | pgo.Object 16 | client *Client 17 | panicRecover bool 18 | } 19 | 20 | func (a *Adapter) Construct(componentId ...string) { 21 | id := defaultComponentId 22 | if len(componentId) > 0 { 23 | id = componentId[0] 24 | } 25 | 26 | a.client = pgo.App.Get(id).(*Client) 27 | a.panicRecover = true 28 | } 29 | 30 | func (a *Adapter) SetPanicRecover(v bool) { 31 | a.panicRecover = v 32 | } 33 | 34 | func (a *Adapter) GetClient() *Client { 35 | return a.client 36 | } 37 | 38 | func (a *Adapter) handlePanic() { 39 | if a.panicRecover { 40 | if v := recover(); v != nil { 41 | a.GetContext().Error(Util.ToString(v)) 42 | } 43 | } 44 | } 45 | 46 | // Get perform a get request 47 | func (a *Adapter) Get(addr string, data interface{}, option ...*Option) *http.Response { 48 | profile := baseUrl(addr) 49 | a.GetContext().ProfileStart(profile) 50 | defer a.GetContext().ProfileStop(profile) 51 | defer a.handlePanic() 52 | 53 | return a.client.Get(addr, data, option...) 54 | } 55 | 56 | // Post perform a post request 57 | func (a *Adapter) Post(addr string, data interface{}, option ...*Option) *http.Response { 58 | profile := baseUrl(addr) 59 | a.GetContext().ProfileStart(profile) 60 | defer a.GetContext().ProfileStop(profile) 61 | defer a.handlePanic() 62 | 63 | return a.client.Post(addr, data, option...) 64 | } 65 | 66 | // Do perform a single request 67 | func (a *Adapter) Do(req *http.Request, option ...*Option) *http.Response { 68 | profile := baseUrl(req.URL.String()) 69 | a.GetContext().ProfileStart(profile) 70 | defer a.GetContext().ProfileStop(profile) 71 | defer a.handlePanic() 72 | 73 | return a.client.Do(req, option...) 74 | } 75 | 76 | // DoMulti perform multi requests concurrently 77 | func (a *Adapter) DoMulti(requests []*http.Request, option ...*Option) []*http.Response { 78 | if num := len(option); num != 0 && num != len(requests) { 79 | panic("http multi request invalid num of options") 80 | } 81 | 82 | profile := "Http.DoMulti" 83 | a.GetContext().ProfileStart(profile) 84 | defer a.GetContext().ProfileStop(profile) 85 | defer a.handlePanic() 86 | 87 | lock, wg := new(sync.Mutex), new(sync.WaitGroup) 88 | responses := make([]*http.Response, len(requests)) 89 | 90 | fn := func(k int) { 91 | start, profile := time.Now(), baseUrl(requests[k].URL.String()) 92 | var res *http.Response 93 | 94 | defer func() { 95 | if v := recover(); v != nil { 96 | a.GetContext().Error(Util.ToString(v)) 97 | } 98 | 99 | lock.Lock() 100 | a.GetContext().ProfileAdd(profile, time.Since(start)/1e6) 101 | responses[k] = res 102 | lock.Unlock() 103 | wg.Done() 104 | }() 105 | 106 | if len(option) > 0 && option[k] != nil { 107 | res = a.client.Do(requests[k], option[k]) 108 | } else { 109 | res = a.client.Do(requests[k]) 110 | } 111 | } 112 | 113 | wg.Add(len(requests)) 114 | for k := range requests { 115 | go fn(k) 116 | } 117 | 118 | wg.Wait() 119 | return responses 120 | } 121 | -------------------------------------------------------------------------------- /Client/Redis/Init.go: -------------------------------------------------------------------------------- 1 | package Redis 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/pinguo/pgo" 7 | ) 8 | 9 | const ( 10 | AdapterClass = "@pgo/Client/Redis/Adapter" 11 | 12 | defaultComponentId = "redis" 13 | defaultServer = "127.0.0.1:6379" 14 | defaultPrefix = "pgo_" 15 | defaultPassword = "" 16 | defaultDb = 0 17 | defaultProbe = 0 18 | defaultIdleConn = 10 19 | defaultIdleTime = 60 * time.Second 20 | defaultTimeout = 1 * time.Second 21 | defaultExpire = 24 * time.Hour 22 | 23 | ModCluster = "cluster" 24 | ModMasterSlave = "masterSlave" 25 | 26 | 27 | maxProbeInterval = 30 * time.Second 28 | minProbeInterval = 1 * time.Second 29 | 30 | errBase = "redis: " 31 | errSetProp = "redis: failed to set %s, %s" 32 | errNoServer = "redis: no server available" 33 | errInvalidResp = "redis: invalid resp type, " 34 | errSendFailed = "redis: send request failed, " 35 | errReadFailed = "redis: read response failed, " 36 | errCorrupted = "redis: corrupted response, " 37 | 38 | PgoMasterSlaveCheckPrefix = "pgo_master_slave_check_" 39 | 40 | NodeActionAdd = "add" 41 | NodeActionDel = "del" 42 | ) 43 | 44 | var ( 45 | lineEnding = []byte("\r\n") 46 | replyOK = []byte("OK") 47 | replyPong = []byte("PONG") 48 | allMod = []string{ModCluster, ModMasterSlave} 49 | 50 | allRedisCmd = []string{ 51 | // Strings 52 | "DECR", "DECRBY", "GETSET", "INCR", "INCRBY", "INCRBYFLOAT", 53 | "SETEX", "PSETEX", "SETNX", 54 | 55 | // Keys 56 | "EXISTS", "EXPIRE", "PEXPIRE", "EXPIREAT", "PEXPIREAT", 57 | "PERSIST", "RENAME", "RENAMENX", "TYPE", "TTL", "PTTL", 58 | 59 | // Hashes 60 | "HDEL", "HEXISTS", "HGET", "HGETALL", "HINCRBY", "HINCRBYFLOAT", "HKEYS", 61 | "HLEN", "HMGET", "HMSET", "HSET","HSETNX", "HVALS", 62 | 63 | // List 64 | "BLPOP", "BRPOP", "LINDEX", "LGET", "LINSERT", "LLEN", 65 | "LPOP", "LPUSH", "LPUSHX", "LRANGE", "LREM", "LGETRANGE", "LSET", 66 | "LTRIM", "RPOP", "RPUSH", "RPUSHX", 67 | 68 | // Set 69 | "SADD", "SCARD", "SISMEMBER","SMEMBERS", "SPOP", "SRANDMEMBER", "SREM", 70 | 71 | // Sorted Set 72 | "ZADD", "ZCARD", "ZCOUNT", "ZINCRBY", "ZRANGE", "ZRANGEBYSCORE", "ZREVRANGEBYSCORE", 73 | "ZRANK", "ZREVRANK", "ZREM", "ZREMRANGEBYRANK", "ZREMRANGEBYSCORE", "ZREVRANGE", "ZSCORE", 74 | } 75 | 76 | allRedisReadCmd = []string{ 77 | // Strings 78 | "GET", "MGET", "BITCOUNT", "STRLEN", "GETBIT", "GETRANGE", 79 | // Keys 80 | "KEYS", "TYPE", "SCAN", "EXISTS", "PTTL", "TTL", 81 | // Hashes 82 | "HEXISTS", "HGETALL", "HKEYS", "HLEN", "HGET", "HMGET", 83 | // Set 84 | "SISMEMBER", "SMEMBERS", "SRANDMEMBER", "SSCAN", "SCARD", "SDIFF", "SINTER", 85 | // List 86 | "LINDEX", "LLEN", "LRANGE", 87 | // Sorted Set 88 | "ZCARD", "ZCOUNT", "ZRANGE", "ZRANGEBYSCORE", "ZRANK", "ZREVRANGE", "ZREVRANGEBYSCORE", 89 | "ZREVRANK", "ZSCAN", "ZSCORE", 90 | } 91 | ) 92 | 93 | func init() { 94 | container := pgo.App.GetContainer() 95 | 96 | container.Bind(&Adapter{}) 97 | container.Bind(&Client{}) 98 | 99 | } 100 | 101 | func keys2Args(keys []string) []interface{} { 102 | args := make([]interface{}, len(keys)) 103 | for i, k := range keys { 104 | args[i] = k 105 | } 106 | return args 107 | } 108 | 109 | type IPool interface { 110 | startCheck() 111 | check(addr, aType string) 112 | getAddrByKey(cmd, key, prevDft string) string 113 | } 114 | -------------------------------------------------------------------------------- /Util/Path.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Julien Schmidt. All rights reserved. 2 | // Based on the path package, Copyright 2009 The Go Authors. 3 | // Use of this source code is governed by a BSD-style license that can be found 4 | // in the LICENSE file. 5 | 6 | package Util 7 | 8 | // CleanPath is the URL version of path.Clean, it returns a canonical URL path 9 | // for p, eliminating . and .. elements. 10 | // 11 | // The following rules are applied iteratively until no further processing can 12 | // be done: 13 | // 1. Replace multiple slashes with a single slash. 14 | // 2. Eliminate each . path name element (the current directory). 15 | // 3. Eliminate each inner .. path name element (the parent directory) 16 | // along with the non-.. element that precedes it. 17 | // 4. Eliminate .. elements that begin a rooted path: 18 | // that is, replace "/.." by "/" at the beginning of a path. 19 | // 20 | // If the result of this process is an empty string, "/" is returned 21 | func CleanPath(p string) string { 22 | // Turn empty string into "/" 23 | if p == "" { 24 | return "/" 25 | } 26 | 27 | n := len(p) 28 | var buf []byte 29 | 30 | // Invariants: 31 | // reading from path; r is index of next byte to process. 32 | // writing to buf; w is index of next byte to write. 33 | 34 | // path must start with '/' 35 | r := 1 36 | w := 1 37 | 38 | if p[0] != '/' { 39 | r = 0 40 | buf = make([]byte, n+1) 41 | buf[0] = '/' 42 | } 43 | 44 | trailing := n > 2 && p[n-1] == '/' 45 | 46 | // A bit more clunky without a 'lazybuf' like the path package, but the loop 47 | // gets completely inlined (bufApp). So in contrast to the path package this 48 | // loop has no expensive function calls (except 1x make) 49 | 50 | for r < n { 51 | switch { 52 | case p[r] == '/': 53 | // empty path element, trailing slash is added after the end 54 | r++ 55 | 56 | case p[r] == '.' && r+1 == n: 57 | trailing = true 58 | r++ 59 | 60 | case p[r] == '.' && p[r+1] == '/': 61 | // . element 62 | r++ 63 | 64 | case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'): 65 | // .. element: remove to last / 66 | r += 2 67 | 68 | if w > 1 { 69 | // can backtrack 70 | w-- 71 | 72 | if buf == nil { 73 | for w > 1 && p[w] != '/' { 74 | w-- 75 | } 76 | } else { 77 | for w > 1 && buf[w] != '/' { 78 | w-- 79 | } 80 | } 81 | } 82 | 83 | default: 84 | // real path element. 85 | // add slash if needed 86 | if w > 1 { 87 | bufApp(&buf, p, w, '/') 88 | w++ 89 | } 90 | 91 | // copy element 92 | for r < n && p[r] != '/' { 93 | bufApp(&buf, p, w, p[r]) 94 | w++ 95 | r++ 96 | } 97 | } 98 | } 99 | 100 | // re-append trailing slash 101 | if trailing && w > 1 { 102 | bufApp(&buf, p, w, '/') 103 | w++ 104 | } 105 | 106 | if buf == nil { 107 | return p[:w] 108 | } 109 | return string(buf[:w]) 110 | } 111 | 112 | // internal helper to lazily create a buffer if necessary 113 | func bufApp(buf *[]byte, s string, w int, c byte) { 114 | if *buf == nil { 115 | if s[w] == c { 116 | return 117 | } 118 | 119 | *buf = make([]byte, len(s)) 120 | copy(*buf, s[:w]) 121 | } 122 | (*buf)[w] = c 123 | } 124 | -------------------------------------------------------------------------------- /Controller.go: -------------------------------------------------------------------------------- 1 | package pgo 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "reflect" 9 | 10 | "github.com/pinguo/pgo/Util" 11 | ) 12 | 13 | // Controller the base class of web and cmd controller 14 | type Controller struct { 15 | Object 16 | } 17 | 18 | // GetBindInfo get action map as extra binding info 19 | func (c *Controller) GetBindInfo(v interface{}) interface{} { 20 | if _, ok := v.(IController); !ok { 21 | panic("param require a controller") 22 | } 23 | 24 | rt := reflect.ValueOf(v).Type() 25 | num := rt.NumMethod() 26 | actions := make(map[string]int) 27 | 28 | for i := 0; i < num; i++ { 29 | name := rt.Method(i).Name 30 | if len(name) > ActionLength && name[:ActionLength] == ActionPrefix { 31 | actions[name[ActionLength:]] = i 32 | } 33 | } 34 | 35 | return actions 36 | } 37 | 38 | // BeforeAction before action hook 39 | func (c *Controller) BeforeAction(action string) { 40 | } 41 | 42 | // AfterAction after action hook 43 | func (c *Controller) AfterAction(action string) { 44 | } 45 | 46 | // HandlePanic process unhandled action panic 47 | func (c *Controller) HandlePanic(v interface{}) { 48 | status := http.StatusInternalServerError 49 | switch e := v.(type) { 50 | case *Exception: 51 | status = e.GetStatus() 52 | c.OutputJson(EmptyObject, status, e.GetMessage()) 53 | default: 54 | c.OutputJson(EmptyObject, status) 55 | } 56 | 57 | c.GetContext().Error("%s, trace[%s]", Util.ToString(v), Util.PanicTrace(TraceMaxDepth, false)) 58 | } 59 | 60 | // Redirect output redirect response 61 | func (c *Controller) Redirect(location string, permanent bool) { 62 | ctx := c.GetContext() 63 | ctx.SetHeader("Location", location) 64 | if permanent { 65 | ctx.End(http.StatusMovedPermanently, nil) 66 | } else { 67 | ctx.End(http.StatusFound, nil) 68 | } 69 | } 70 | 71 | // OutputJson output json response 72 | func (c *Controller) OutputJson(data interface{}, status int, msg ...string) { 73 | ctx := c.GetContext() 74 | message := App.GetStatus().GetText(status, ctx, msg...) 75 | output, e := json.Marshal(map[string]interface{}{ 76 | "status": status, 77 | "message": message, 78 | "data": data, 79 | }) 80 | 81 | if e != nil { 82 | panic(fmt.Sprintf("failed to marshal json, %s", e)) 83 | } 84 | 85 | ctx.PushLog("status", status) 86 | ctx.SetHeader("Content-Type", "application/json; charset=utf-8") 87 | ctx.End(http.StatusOK, output) 88 | } 89 | 90 | // OutputJsonp output jsonp response 91 | func (c *Controller) OutputJsonp(callback string, data interface{}, status int, msg ...string) { 92 | ctx := c.GetContext() 93 | message := App.GetStatus().GetText(status, ctx, msg...) 94 | output, e := json.Marshal(map[string]interface{}{ 95 | "status": status, 96 | "message": message, 97 | "data": data, 98 | }) 99 | 100 | if e != nil { 101 | panic(fmt.Sprintf("failed to marshal json, %s", e)) 102 | } 103 | 104 | buf := &bytes.Buffer{} 105 | buf.WriteString(callback + "(") 106 | buf.Write(output) 107 | buf.WriteString(")") 108 | 109 | ctx.PushLog("status", status) 110 | ctx.SetHeader("Content-Type", "text/javascript; charset=utf-8") 111 | ctx.End(http.StatusOK, buf.Bytes()) 112 | } 113 | 114 | // OutputView output rendered view 115 | func (c *Controller) OutputView(view string, data interface{}, contentType ...string) { 116 | ctx := c.GetContext() 117 | contentType = append(contentType, "text/html; charset=utf-8") 118 | ctx.PushLog("status", http.StatusOK) 119 | ctx.SetHeader("Content-Type", contentType[0]) 120 | ctx.End(http.StatusOK, App.GetView().Render(view, data)) 121 | } 122 | -------------------------------------------------------------------------------- /Client/Db/Stmt.go: -------------------------------------------------------------------------------- 1 | package Db 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "time" 7 | 8 | "github.com/pinguo/pgo" 9 | ) 10 | 11 | // Stmt wrap sql.Stmt, add context support 12 | type Stmt struct { 13 | pgo.Object 14 | stmt *sql.Stmt 15 | client *Client 16 | query string 17 | } 18 | 19 | // Close close sql.Stmt and return instance to pool 20 | func (s *Stmt) Close() { 21 | s.SetContext(nil) 22 | s.stmt.Close() 23 | s.stmt = nil 24 | s.query = "" 25 | stmtPool.Put(s) 26 | } 27 | 28 | // QueryOne perform one row query using a default timeout context, 29 | // and always returns a non-nil value, Errors are deferred until 30 | // Row's Scan method is called. 31 | func (s *Stmt) QueryOne(args ...interface{}) *Row { 32 | ctx, _ := context.WithTimeout(context.Background(), defaultTimeout) 33 | return s.QueryOneContext(ctx, args...) 34 | } 35 | 36 | // QueryOneContext perform one row query using a specified context, 37 | // and always returns a non-nil value, Errors are deferred until 38 | // Row's Scan method is called. 39 | func (s *Stmt) QueryOneContext(ctx context.Context, args ...interface{}) *Row { 40 | start := time.Now() 41 | defer func() { 42 | elapse := time.Since(start) 43 | s.GetContext().ProfileAdd("Db.StmtQueryOne", elapse) 44 | 45 | if elapse >= s.client.slowLogTime && s.client.slowLogTime > 0 { 46 | s.GetContext().Warn("Db.StmtQueryOne slow, elapse:%s, query:%s, args:%v", elapse, s.query, args) 47 | } 48 | }() 49 | 50 | row := s.stmt.QueryRowContext(ctx, args...) 51 | 52 | // wrap row for profile purpose 53 | rowWrapper := rowPool.Get().(*Row) 54 | rowWrapper.SetContext(s.GetContext()) 55 | rowWrapper.row = row 56 | rowWrapper.query = s.query 57 | rowWrapper.args = args 58 | 59 | return rowWrapper 60 | } 61 | 62 | // Query perform query using a default timeout context. 63 | func (s *Stmt) Query(args ...interface{}) *sql.Rows { 64 | ctx, _ := context.WithTimeout(context.Background(), defaultTimeout) 65 | return s.QueryContext(ctx, args...) 66 | } 67 | 68 | // QueryContext perform query using a specified context. 69 | func (s *Stmt) QueryContext(ctx context.Context, args ...interface{}) *sql.Rows { 70 | start := time.Now() 71 | defer func() { 72 | elapse := time.Since(start) 73 | s.GetContext().ProfileAdd("Db.StmtQuery", elapse) 74 | 75 | if elapse >= s.client.slowLogTime && s.client.slowLogTime > 0 { 76 | s.GetContext().Warn("Db.StmtQuery slow, elapse:%s, query:%s, args:%v", elapse, s.query, args) 77 | } 78 | }() 79 | 80 | rows, err := s.stmt.QueryContext(ctx, args...) 81 | if err != nil { 82 | s.GetContext().Error("Db.StmtQuery error, %s, query:%s, args:%v", err.Error(), s.query, args) 83 | return nil 84 | } 85 | 86 | return rows 87 | } 88 | 89 | // Exec perform exec using a default timeout context. 90 | func (s *Stmt) Exec(args ...interface{}) sql.Result { 91 | ctx, _ := context.WithTimeout(context.Background(), defaultTimeout) 92 | return s.ExecContext(ctx, args...) 93 | } 94 | 95 | // ExecContext perform exec using a specified context. 96 | func (s *Stmt) ExecContext(ctx context.Context, args ...interface{}) sql.Result { 97 | start := time.Now() 98 | defer func() { 99 | elapse := time.Since(start) 100 | s.GetContext().ProfileAdd("Db.StmtExec", elapse) 101 | 102 | if elapse >= s.client.slowLogTime && s.client.slowLogTime > 0 { 103 | s.GetContext().Warn("Db.StmtExec slow, elapse:%s, query:%s, args:%v", elapse, s.query, args) 104 | } 105 | }() 106 | 107 | res, err := s.stmt.ExecContext(ctx, args...) 108 | if err != nil { 109 | s.GetContext().Error("Db.StmtExec error, %s, query:%s, args:%v", err.Error(), s.query, args) 110 | return nil 111 | } 112 | 113 | return res 114 | } 115 | -------------------------------------------------------------------------------- /Client/Memory/Adapter.go: -------------------------------------------------------------------------------- 1 | package Memory 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/pinguo/pgo" 7 | "github.com/pinguo/pgo/Util" 8 | ) 9 | 10 | // Adapter of Memory Client, add context support. 11 | // usage: memory := this.GetObject(Memory.AdapterClass).(*Memory.Adapter) 12 | type Adapter struct { 13 | pgo.Object 14 | client *Client 15 | panicRecover bool 16 | } 17 | 18 | func (a *Adapter) Construct(componentId ...string) { 19 | id := defaultComponentId 20 | if len(componentId) > 0 { 21 | id = componentId[0] 22 | } 23 | 24 | a.client = pgo.App.Get(id).(*Client) 25 | a.panicRecover = true 26 | } 27 | 28 | func (a *Adapter) SetPanicRecover(v bool) { 29 | a.panicRecover = v 30 | } 31 | 32 | func (a *Adapter) GetClient() *Client { 33 | return a.client 34 | } 35 | 36 | func (a *Adapter) handlePanic() { 37 | if a.panicRecover { 38 | if v := recover(); v != nil { 39 | a.GetContext().Error(Util.ToString(v)) 40 | } 41 | } 42 | } 43 | 44 | func (a *Adapter) Get(key string) *pgo.Value { 45 | profile := "Memory.Get" 46 | a.GetContext().ProfileStart(profile) 47 | defer a.GetContext().ProfileStop(profile) 48 | defer a.handlePanic() 49 | 50 | res, hit := a.client.Get(key), 0 51 | if res != nil && res.Valid() { 52 | hit = 1 53 | } 54 | 55 | a.GetContext().Counting(profile, hit, 1) 56 | return res 57 | } 58 | 59 | func (a *Adapter) MGet(keys []string) map[string]*pgo.Value { 60 | profile := "Memory.MGet" 61 | a.GetContext().ProfileStart(profile) 62 | defer a.GetContext().ProfileStop(profile) 63 | defer a.handlePanic() 64 | 65 | res, hit := a.client.MGet(keys), 0 66 | for _, v := range res { 67 | if v != nil && v.Valid() { 68 | hit += 1 69 | } 70 | } 71 | 72 | a.GetContext().Counting(profile, hit, len(keys)) 73 | return res 74 | } 75 | 76 | func (a *Adapter) Set(key string, value interface{}, expire ...time.Duration) bool { 77 | profile := "Memory.Set" 78 | a.GetContext().ProfileStart(profile) 79 | defer a.GetContext().ProfileStop(profile) 80 | defer a.handlePanic() 81 | 82 | return a.client.Set(key, value, expire...) 83 | } 84 | 85 | func (a *Adapter) MSet(items map[string]interface{}, expire ...time.Duration) bool { 86 | profile := "Memory.MSet" 87 | a.GetContext().ProfileStart(profile) 88 | defer a.GetContext().ProfileStop(profile) 89 | defer a.handlePanic() 90 | 91 | return a.client.MSet(items, expire...) 92 | } 93 | 94 | func (a *Adapter) Add(key string, value interface{}, expire ...time.Duration) bool { 95 | profile := "Memory.Add" 96 | a.GetContext().ProfileStart(profile) 97 | defer a.GetContext().ProfileStop(profile) 98 | defer a.handlePanic() 99 | 100 | return a.client.Add(key, value, expire...) 101 | } 102 | 103 | func (a *Adapter) MAdd(items map[string]interface{}, expire ...time.Duration) bool { 104 | profile := "Memory.MAdd" 105 | a.GetContext().ProfileStart(profile) 106 | defer a.GetContext().ProfileStart(profile) 107 | defer a.handlePanic() 108 | 109 | return a.client.MAdd(items, expire...) 110 | } 111 | 112 | func (a *Adapter) Del(key string) bool { 113 | profile := "Memory.Del" 114 | a.GetContext().ProfileStart(profile) 115 | defer a.GetContext().ProfileStop(profile) 116 | defer a.handlePanic() 117 | 118 | return a.client.Del(key) 119 | } 120 | 121 | func (a *Adapter) MDel(keys []string) bool { 122 | profile := "Memory.MDel" 123 | a.GetContext().ProfileStart(profile) 124 | defer a.GetContext().ProfileStop(profile) 125 | defer a.handlePanic() 126 | 127 | return a.client.MDel(keys) 128 | } 129 | 130 | func (a *Adapter) Exists(key string) bool { 131 | profile := "Memory.Exists" 132 | a.GetContext().ProfileStart(profile) 133 | defer a.GetContext().ProfileStop(profile) 134 | defer a.handlePanic() 135 | 136 | return a.client.Exists(key) 137 | } 138 | 139 | func (a *Adapter) Incr(key string, delta int) int { 140 | profile := "Memory.Incr" 141 | a.GetContext().ProfileStart(profile) 142 | defer a.GetContext().ProfileStop(profile) 143 | defer a.handlePanic() 144 | 145 | return a.client.Incr(key, delta) 146 | } 147 | -------------------------------------------------------------------------------- /Client/MaxMind/Client.go: -------------------------------------------------------------------------------- 1 | package MaxMind 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strings" 7 | 8 | "github.com/oschwald/maxminddb-golang" 9 | "github.com/pinguo/pgo" 10 | ) 11 | 12 | // Geo lookup result 13 | type Geo struct { 14 | Code string `json:"code"` // country/area code 15 | Continent string `json:"-"` // continent name (en) 16 | Country string `json:"country,omitempty"` // country/area name (en) 17 | Province string `json:"province,omitempty"` // province name (en) 18 | City string `json:"city,omitempty"` // city name(en) 19 | 20 | // i18n name, default is en 21 | I18n struct { 22 | Continent string 23 | Country string 24 | Province string 25 | City string 26 | } `json:"-"` 27 | } 28 | 29 | // MaxMind Client component, configuration: 30 | // maxMind: 31 | // class: "@pgo/Client/MaxMind/Client" 32 | // countryFile: "@app/../geoip/GeoLite2-Country.mmdb" 33 | // cityFile: "@app/../geoip/GeoLite2-City.mmdb" 34 | type Client struct { 35 | readers [2]*maxminddb.Reader 36 | } 37 | 38 | func (c *Client) Init() { 39 | if c.readers[DBCountry] == nil && c.readers[DBCity] == nil { 40 | panic("MaxMind: both country and city db are empty") 41 | } 42 | } 43 | 44 | func (c *Client) SetCountryFile(path string) { 45 | c.loadFile(DBCountry, path) 46 | } 47 | 48 | func (c *Client) SetCityFile(path string) { 49 | c.loadFile(DBCity, path) 50 | } 51 | 52 | // get geo info by ip, optional args: 53 | // db int: preferred geo db 54 | // lang string: preferred i18n language 55 | func (c *Client) GeoByIp(ip string, args ...interface{}) *Geo { 56 | db := DBCity 57 | lang := defaultLang 58 | 59 | // parse optional args 60 | for _, arg := range args { 61 | switch v := arg.(type) { 62 | case int: 63 | db = v 64 | case string: 65 | lang = v 66 | default: 67 | panic(fmt.Sprintf("MaxMind: invalid arg type: %T", arg)) 68 | } 69 | } 70 | 71 | // get available db reader 72 | if c.readers[db] == nil { 73 | db = (db + 1) % 2 74 | } 75 | 76 | var m map[string]interface{} 77 | if e := c.readers[db].Lookup(net.ParseIP(ip), &m); e != nil { 78 | panic(fmt.Sprintf("MaxMind: failed to parse ip, ip:%s, err:%s", ip, e)) 79 | } 80 | 81 | if len(m) == 0 { 82 | return nil 83 | } 84 | 85 | geo := &Geo{} 86 | for k, v := range m { 87 | switch k { 88 | case "continent": 89 | vm, _ := v.(map[string]interface{}) 90 | geo.Continent = c.getI18nName(vm, defaultLang) 91 | geo.I18n.Continent = c.getI18nName(vm, lang) 92 | case "country": 93 | vm, _ := v.(map[string]interface{}) 94 | geo.Code = vm["iso_code"].(string) 95 | geo.Country = c.getI18nName(vm, defaultLang) 96 | geo.I18n.Country = c.getI18nName(vm, lang) 97 | case "city": 98 | vm, _ := v.(map[string]interface{}) 99 | geo.City = c.getI18nName(vm, defaultLang) 100 | geo.I18n.City = c.getI18nName(vm, lang) 101 | case "subdivisions": 102 | if vs, _ := v.([]interface{}); len(vs) > 0 { 103 | vm, _ := vs[0].(map[string]interface{}) 104 | geo.Province = c.getI18nName(vm, defaultLang) 105 | geo.I18n.Province = c.getI18nName(vm, lang) 106 | } 107 | } 108 | } 109 | 110 | return geo 111 | } 112 | 113 | func (c *Client) loadFile(db int, path string) { 114 | if reader, e := maxminddb.Open(pgo.GetAlias(path)); e != nil { 115 | panic(fmt.Sprintf("MaxMind: failed to open file, path:%s, err:%s", path, e)) 116 | } else { 117 | c.readers[db] = reader 118 | } 119 | } 120 | 121 | func (c *Client) getI18nName(m map[string]interface{}, lang string) string { 122 | names, _ := m["names"].(map[string]interface{}) 123 | 124 | if n, ok := names[lang]; ok { 125 | return n.(string) 126 | } else if p := strings.IndexAny(lang, "_-"); p > 0 { 127 | l := lang[:p] 128 | if n, ok := names[l]; ok { 129 | return n.(string) 130 | } 131 | } 132 | 133 | if n, ok := names[defaultLang]; ok { 134 | return n.(string) 135 | } 136 | 137 | return "" 138 | } 139 | -------------------------------------------------------------------------------- /Client/Db/Client.go: -------------------------------------------------------------------------------- 1 | package Db 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/pinguo/pgo/Util" 9 | ) 10 | 11 | // DB client component, wrapper for database/sql, 12 | // support read-write splitting, configuration: 13 | // db: 14 | // class: "@pgo/Client/Db/Client" 15 | // driver: "mysql" 16 | // dsn: "user:pass@tcp(127.0.0.1:3306)/db?charset=utf8&timeout=0.5s" 17 | // slaves: ["slave1 dsn", "slave2 dsn"] 18 | // maxIdleConn: 5 19 | // maxOpenConn: 10 20 | // sqlLog:false 21 | // maxConnTime: "1h" 22 | // slowLogTime: "100ms" 23 | type Client struct { 24 | driver string // driver name 25 | dsn string // master dsn 26 | slaves []string // slaves dsn 27 | 28 | maxIdleConn int 29 | maxConnTime time.Duration 30 | slowLogTime time.Duration 31 | maxOpenConn int 32 | 33 | sqlLog bool // Complete SQL log 34 | 35 | masterDb *sql.DB // master db instance 36 | slaveDbs []*sql.DB // slave db instances 37 | } 38 | 39 | func (c *Client) Construct() { 40 | c.maxIdleConn = 5 41 | c.maxOpenConn = 0 42 | c.maxConnTime = time.Hour 43 | c.slowLogTime = 100 * time.Millisecond 44 | c.sqlLog = false 45 | } 46 | 47 | func (c *Client) Init() { 48 | if c.driver == "" || c.dsn == "" { 49 | panic("Db: driver and dsn are required") 50 | } 51 | 52 | if Util.SliceSearchString(sql.Drivers(), c.driver) == -1 { 53 | panic(fmt.Sprintf("Db: driver %s is not registered", c.driver)) 54 | } 55 | 56 | // create master db instance 57 | if db, e := sql.Open(c.driver, c.dsn); e != nil { 58 | panic(fmt.Sprintf("Db: open %s error, %s", c.dsn, e.Error())) 59 | } else { 60 | db.SetConnMaxLifetime(c.maxConnTime) 61 | db.SetMaxIdleConns(c.maxIdleConn) 62 | db.SetMaxOpenConns(c.maxOpenConn) 63 | c.masterDb = db 64 | } 65 | 66 | // create slave db instances 67 | for _, dsn := range c.slaves { 68 | if db, e := sql.Open(c.driver, dsn); e != nil { 69 | panic(fmt.Sprintf("Db: open %s error, %s", dsn, e.Error())) 70 | } else { 71 | db.SetConnMaxLifetime(c.maxConnTime) 72 | db.SetMaxIdleConns(c.maxIdleConn) 73 | db.SetMaxOpenConns(c.maxOpenConn) 74 | c.slaveDbs = append(c.slaveDbs, db) 75 | } 76 | } 77 | } 78 | 79 | // SetDriver set driver db use, eg. "mysql" 80 | func (c *Client) SetDriver(driver string) { 81 | c.driver = driver 82 | } 83 | 84 | // SetDsn set master dsn, the dsn is driver specified, 85 | // eg. dsn format for github.com/go-sql-driver/mysql is 86 | // [username[:password]@][protocol[(address)]]/dbname[?param=value] 87 | func (c *Client) SetDsn(dsn string) { 88 | c.dsn = dsn 89 | } 90 | 91 | // SetSlaves set dsn for slaves 92 | func (c *Client) SetSlaves(v []interface{}) { 93 | for _, vv := range v { 94 | c.slaves = append(c.slaves, vv.(string)) 95 | } 96 | } 97 | 98 | // SetMaxIdleConn set max idle conn, default is 5 99 | func (c *Client) SetMaxIdleConn(maxIdleConn int) { 100 | c.maxIdleConn = maxIdleConn 101 | } 102 | 103 | // SetMaxOpenConn set max open conn, default is 0 104 | func (c *Client) SetMaxOpenConn(maxOpenConn int) { 105 | c.maxOpenConn = maxOpenConn 106 | } 107 | 108 | // SetMaxConnTime set conn life time, default is 1h 109 | func (c *Client) SetMaxConnTime(v string) { 110 | if maxConnTime, err := time.ParseDuration(v); err != nil { 111 | panic("Db.SetMaxConnTime error, " + err.Error()) 112 | } else { 113 | c.maxConnTime = maxConnTime 114 | } 115 | } 116 | 117 | // SetSlowTime set slow log time, default is 100ms 118 | func (c *Client) SetSlowLogTime(v string) { 119 | if slowLogTime, err := time.ParseDuration(v); err != nil { 120 | panic("Db.SetSlowLogTime error, " + err.Error()) 121 | } else { 122 | c.slowLogTime = slowLogTime 123 | } 124 | } 125 | 126 | func (c *Client) SetSqlLog(sqlLog bool) { 127 | c.sqlLog = sqlLog 128 | } 129 | 130 | // GetDb get a master or slave db instance 131 | func (c *Client) GetDb(master bool) *sql.DB { 132 | if num := len(c.slaveDbs); !master && num > 0 { 133 | idx := 0 134 | if num > 1 { 135 | idx = (time.Now().Nanosecond() / 1000) % num 136 | } 137 | 138 | return c.slaveDbs[idx] 139 | } 140 | 141 | return c.masterDb 142 | } 143 | -------------------------------------------------------------------------------- /Client/RabbitMq/ConnBox.go: -------------------------------------------------------------------------------- 1 | package RabbitMq 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "io/ioutil" 7 | "sync" 8 | "time" 9 | 10 | "github.com/pinguo/pgo" 11 | "github.com/pinguo/pgo/Util" 12 | "github.com/streadway/amqp" 13 | ) 14 | 15 | func newConnBox(id, addr, dsn string, maxChannelNum int, tlsDft ...string) *ConnBox { 16 | tlsCert, tlsCertKey, tlsRootCAs := "", "", "" 17 | if len(tlsDft) > 0 { 18 | tlsCert = tlsDft[0] 19 | tlsCertKey = tlsDft[1] 20 | tlsRootCAs = tlsDft[2] 21 | } 22 | 23 | connBox := &ConnBox{id: id, addr: addr, dsn: dsn, tlsCert: tlsCert, tlsCertKey: tlsCertKey, tlsRootCAs: tlsRootCAs, maxChannelNum: maxChannelNum} 24 | connBox.initConn() 25 | 26 | return connBox 27 | } 28 | 29 | type ConnBox struct { 30 | id string 31 | addr string 32 | useConnCount int 33 | useChannelCount int 34 | channelList chan *ChannelBox 35 | lock sync.RWMutex 36 | newChannelLock sync.RWMutex 37 | startTime time.Time 38 | 39 | connection *amqp.Connection 40 | tlsCert string 41 | tlsCertKey string 42 | tlsRootCAs string 43 | dsn string 44 | 45 | maxChannelNum int 46 | 47 | notifyClose chan *amqp.Error 48 | 49 | disable bool 50 | } 51 | 52 | func (c *ConnBox) setEnable() { 53 | c.lock.Lock() 54 | defer c.lock.Unlock() 55 | c.disable = false 56 | } 57 | 58 | func (c *ConnBox) setDisable() { 59 | c.lock.Lock() 60 | defer c.lock.Unlock() 61 | c.disable = true 62 | c.close() 63 | } 64 | 65 | func (c *ConnBox) initConn() { 66 | func() { 67 | c.lock.Lock() 68 | defer c.lock.Unlock() 69 | var err error 70 | 71 | if c.tlsCert != "" && c.tlsCertKey != "" { 72 | cfg := new(tls.Config) 73 | if c.tlsRootCAs != "" { 74 | cfg.RootCAs = x509.NewCertPool() 75 | if ca, err := ioutil.ReadFile(c.tlsRootCAs); err == nil { 76 | cfg.RootCAs.AppendCertsFromPEM(ca) 77 | } 78 | } 79 | 80 | if cert, err := tls.LoadX509KeyPair(c.tlsCert, c.tlsCertKey); err == nil { 81 | cfg.Certificates = append(cfg.Certificates, cert) 82 | } 83 | 84 | c.connection, err = amqp.DialTLS(c.dsn, cfg) 85 | } else { 86 | c.connection, err = amqp.Dial(c.dsn) 87 | } 88 | 89 | if err != nil { 90 | panic("Failed to connect to RabbitMQ:" + err.Error()) 91 | } 92 | 93 | c.disable = false 94 | c.channelList = make(chan *ChannelBox, c.maxChannelNum) 95 | c.notifyClose = make(chan *amqp.Error) 96 | c.startTime = time.Now() 97 | c.connection.NotifyClose(c.notifyClose) 98 | }() 99 | 100 | go c.check(c.startTime) 101 | } 102 | 103 | func (c *ConnBox) check(startTime time.Time) { 104 | defer func() { 105 | if err := recover(); err != nil { 106 | pgo.GLogger().Error("Rabbit ConnBox.check err:" + Util.ToString(err)) 107 | } 108 | }() 109 | 110 | for { 111 | if c.startTime != startTime { 112 | // 自毁 113 | return 114 | } 115 | 116 | select { 117 | case err, ok := <-c.notifyClose: 118 | if ok == false { 119 | return 120 | } 121 | 122 | if err != nil { 123 | func() { 124 | defer func() { 125 | if err := recover(); err != nil { 126 | pgo.GLogger().Error("Rabbit ConnBox.check start initConn err:" + Util.ToString(err)) 127 | } 128 | }() 129 | 130 | c.setDisable() 131 | c.initConn() 132 | }() 133 | return 134 | } 135 | 136 | default: 137 | time.Sleep(100 * time.Microsecond) 138 | 139 | } 140 | } 141 | } 142 | 143 | func (c *ConnBox) isClosed() bool { 144 | if c.disable || c.connection.IsClosed() { 145 | return true 146 | } 147 | return false 148 | } 149 | 150 | func (c *ConnBox) close() { 151 | if c.connection != nil && c.connection.IsClosed() == false { 152 | err := c.connection.Close() 153 | if err != nil { 154 | pgo.GLogger().Warn("Rabbit ConnBox.close err:" + err.Error()) 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /Client/Redis/Conn.go: -------------------------------------------------------------------------------- 1 | package Redis 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "net" 9 | "strconv" 10 | "time" 11 | 12 | "github.com/pinguo/pgo" 13 | ) 14 | 15 | func newConn(addr string, nc net.Conn, pool *Pool) *Conn { 16 | r := bufio.NewReader(nc) 17 | w := bufio.NewWriter(nc) 18 | 19 | return &Conn{ 20 | addr: addr, 21 | nc: nc, 22 | rw: bufio.NewReadWriter(r, w), 23 | pool: pool, 24 | } 25 | } 26 | 27 | type Conn struct { 28 | prev *Conn 29 | next *Conn 30 | lastActive time.Time 31 | 32 | addr string 33 | nc net.Conn 34 | rw *bufio.ReadWriter 35 | pool *Pool 36 | down bool 37 | } 38 | 39 | func (c *Conn) Close(force bool) { 40 | if force || c.down || !c.pool.putFreeConn(c) { 41 | c.nc.Close() 42 | } else { 43 | c.lastActive = time.Now() 44 | } 45 | } 46 | 47 | func (c *Conn) CheckActive() bool { 48 | if time.Since(c.lastActive) < c.pool.maxIdleTime { 49 | return true 50 | } 51 | 52 | c.ExtendDeadLine() 53 | payload, ok := c.Do("PING").([]byte) 54 | return ok && bytes.Equal(payload, replyPong) 55 | } 56 | 57 | func (c *Conn) ExtendDeadLine(deadLine ...time.Duration) bool { 58 | deadLine = append(deadLine, c.pool.netTimeout) 59 | return c.nc.SetDeadline(time.Now().Add(deadLine[0])) == nil 60 | } 61 | 62 | func (c *Conn) Do(cmd string, args ...interface{}) interface{} { 63 | c.WriteCmd(cmd, args...) 64 | return c.ReadReply() 65 | } 66 | 67 | func (c *Conn) WriteCmd(cmd string, args ...interface{}) { 68 | fmt.Fprintf(c.rw, "*%d\r\n$%d\r\n%s\r\n", len(args)+1, len(cmd), cmd) 69 | for _, arg := range args { 70 | argBytes := pgo.Encode(arg) 71 | fmt.Fprintf(c.rw, "$%d\r\n", len(argBytes)) 72 | c.rw.Write(argBytes) 73 | c.rw.Write(lineEnding) 74 | } 75 | 76 | if e := c.rw.Flush(); e != nil { 77 | c.parseError(errSendFailed+e.Error(), true) 78 | } 79 | } 80 | 81 | // read reply from server, 82 | // return []byte, int, nil or slice of these types 83 | func (c *Conn) ReadReply() interface{} { 84 | line, e := c.rw.ReadSlice('\n') 85 | if e != nil { 86 | c.parseError(errReadFailed+e.Error(), true) 87 | } 88 | 89 | if !bytes.HasSuffix(line, lineEnding) { 90 | c.parseError(errCorrupted+"unexpected line ending", true) 91 | } 92 | 93 | payload := line[1 : len(line)-2] 94 | 95 | switch line[0] { 96 | case '+': 97 | // status response: +\r\n, eg. +OK\r\n, +PONG\r\n 98 | if bytes.Equal(payload, replyOK) { 99 | return replyOK 100 | } else if bytes.Equal(payload, replyPong) { 101 | return replyPong 102 | } else { 103 | data := make([]byte, len(payload)) 104 | copy(data, payload) 105 | return data 106 | } 107 | 108 | case '-': 109 | // error response:-\r\n, eg. -Err unknown command\r\n 110 | c.parseError(errBase+string(payload), false) 111 | 112 | case ':': 113 | // integer response: :\r\n, eg. :99\r\n 114 | if n, e := strconv.Atoi(string(payload)); e != nil { 115 | c.parseError(errCorrupted+e.Error(), true) 116 | } else { 117 | return n 118 | } 119 | 120 | case '$': 121 | // bulk string response: $\r\n\r\n, 122 | // -1 for nil response. eg. $7\r\nfoo bar\r\n 123 | if size, e := strconv.Atoi(string(payload)); e != nil { 124 | c.parseError(errCorrupted+e.Error(), true) 125 | } else if size >= 0 { 126 | data := make([]byte, size+2) 127 | if _, e := io.ReadFull(c.rw, data); e != nil { 128 | c.parseError(errCorrupted+e.Error(), true) 129 | } 130 | return data[:size] 131 | } 132 | 133 | case '*': 134 | // multi response: *\r\n$\r\n\r\n[argN...], 135 | // -1 for nil response. eg. *2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n 136 | if argc, e := strconv.Atoi(string(payload)); e != nil { 137 | c.parseError(errCorrupted+e.Error(), true) 138 | } else if argc >= 0 { 139 | argv := make([]interface{}, argc) 140 | for i := range argv { 141 | argv[i] = c.ReadReply() 142 | } 143 | return argv 144 | } 145 | 146 | default: 147 | c.parseError(errInvalidResp+string(line[:1]), true) 148 | } 149 | return nil 150 | } 151 | 152 | func (c *Conn) parseError(err string, fatal bool) { 153 | if fatal { 154 | c.down = true 155 | } 156 | 157 | panic(err) 158 | } 159 | -------------------------------------------------------------------------------- /Util/Conv.go: -------------------------------------------------------------------------------- 1 | package Util 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "reflect" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | func ToBool(v interface{}) bool { 12 | switch val := v.(type) { 13 | case bool: 14 | return val 15 | case float32, float64: 16 | // direct type conversion may cause data loss, use reflection instead 17 | return reflect.ValueOf(v).Float() != 0 18 | case int, int8, int16, int32, int64: 19 | return reflect.ValueOf(v).Int() != 0 20 | case uint, uint8, uint16, uint32, uint64: 21 | return reflect.ValueOf(v).Uint() != 0 22 | case string: 23 | return str2bool(val) 24 | case []byte: 25 | return str2bool(string(val)) 26 | case nil: 27 | return false 28 | default: 29 | rv := reflect.ValueOf(v) 30 | if rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface { 31 | rv = rv.Elem() 32 | } 33 | 34 | // none empty array/slice/map convert to true, otherwise false 35 | if rv.Kind() == reflect.Array || 36 | rv.Kind() == reflect.Slice || 37 | rv.Kind() == reflect.Map { 38 | return rv.Len() != 0 39 | } 40 | 41 | // valid value convert to true, otherwise false 42 | return rv.IsValid() 43 | } 44 | } 45 | 46 | func ToInt(v interface{}) int { 47 | switch val := v.(type) { 48 | case bool: 49 | if val { 50 | return 1 51 | } else { 52 | return 0 53 | } 54 | case float32, float64: 55 | // direct type conversion may cause data loss, use reflection instead 56 | return int(reflect.ValueOf(v).Float()) 57 | case int, int8, int16, int32, int64: 58 | return int(reflect.ValueOf(v).Int()) 59 | case uint, uint8, uint16, uint32, uint64: 60 | return int(reflect.ValueOf(v).Uint()) 61 | case string: 62 | return str2int(val) 63 | case []byte: 64 | return str2int(string(val)) 65 | case nil: 66 | return 0 67 | default: 68 | panic(fmt.Sprintf("ToInt: invalid type: %T", v)) 69 | } 70 | } 71 | 72 | func ToFloat(v interface{}) float64 { 73 | switch val := v.(type) { 74 | case bool: 75 | if val { 76 | return 1 77 | } else { 78 | return 0 79 | } 80 | case float32, float64: 81 | // direct type conversion may cause data loss, use reflection instead 82 | return reflect.ValueOf(v).Float() 83 | case int, int8, int16, int32, int64: 84 | return float64(reflect.ValueOf(v).Int()) 85 | case uint, uint8, uint16, uint32, uint64: 86 | return float64(reflect.ValueOf(v).Uint()) 87 | case string: 88 | return str2float(val) 89 | case []byte: 90 | return str2float(string(val)) 91 | case nil: 92 | return 0 93 | default: 94 | panic(fmt.Sprintf("ToFloat: invalid type: %T", v)) 95 | } 96 | } 97 | 98 | func ToString(v interface{}) string { 99 | switch val := v.(type) { 100 | case bool: 101 | return strconv.FormatBool(val) 102 | case int, int8, int16, int32, int64: 103 | return strconv.FormatInt(reflect.ValueOf(v).Int(), 10) 104 | case uint, uint8, uint16, uint32, uint64: 105 | return strconv.FormatUint(reflect.ValueOf(v).Uint(), 10) 106 | case float32, float64: 107 | return strconv.FormatFloat(reflect.ValueOf(v).Float(), 'g', -1, 64) 108 | case []byte: 109 | return string(val) 110 | case string: 111 | return val 112 | case error: 113 | return val.Error() 114 | case fmt.Stringer: 115 | return val.String() 116 | default: 117 | // convert to json encoded string 118 | if j, e := json.Marshal(v); e == nil { 119 | return string(j) 120 | } 121 | 122 | // convert to default print string 123 | return fmt.Sprintf("%+v", v) 124 | } 125 | } 126 | 127 | func str2bool(s string) bool { 128 | s = strings.TrimSpace(s) 129 | if b, e := strconv.ParseBool(s); e == nil { 130 | return b 131 | } 132 | return len(s) != 0 133 | } 134 | 135 | func str2int(s string) int { 136 | s = strings.TrimSpace(s) 137 | if i64, e := strconv.ParseInt(s, 0, 0); e == nil { 138 | // convert int string(decimal, hexadecimal, octal) 139 | return int(i64) 140 | } else if f64, e := strconv.ParseFloat(s, 64); e == nil { 141 | // convert float string 142 | return int(f64) 143 | } else { 144 | return 0 145 | } 146 | } 147 | 148 | func str2float(s string) float64 { 149 | s = strings.TrimSpace(s) 150 | if f64, e := strconv.ParseFloat(s, 64); e == nil { 151 | // convert float string 152 | return f64 153 | } else if i64, e := strconv.ParseInt(s, 0, 0); e == nil { 154 | // convert int string(decimal, hexadecimal, octal) 155 | return float64(i64) 156 | } else { 157 | return 0 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Container.go: -------------------------------------------------------------------------------- 1 | package pgo 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "sync" 7 | ) 8 | 9 | type bindItem struct { 10 | pool sync.Pool // object pool 11 | info interface{} // binding info 12 | zero reflect.Value // zero value 13 | cmIdx int // construct index 14 | imIdx int // init index 15 | } 16 | 17 | // Container the container component, configuration: 18 | // container: 19 | // enablePool: true 20 | type Container struct { 21 | enablePool bool 22 | items map[string]*bindItem 23 | } 24 | 25 | func (c *Container) Construct() { 26 | c.enablePool = true 27 | c.items = make(map[string]*bindItem) 28 | } 29 | 30 | // SetEnablePool set context based object pool enable or not, default is enabled. 31 | func (c *Container) SetEnablePool(enable bool) { 32 | c.enablePool = enable 33 | } 34 | 35 | // Bind bind template object to class, 36 | // param i must be a pointer of struct. 37 | func (c *Container) Bind(i interface{}) { 38 | iv := reflect.ValueOf(i) 39 | if iv.Kind() != reflect.Ptr { 40 | panic("Container: invalid type, need pointer") 41 | } 42 | 43 | // initialize binding 44 | rt := iv.Elem().Type() 45 | item := bindItem{zero: reflect.Zero(rt), cmIdx: -1, imIdx: -1} 46 | item.pool.New = func() interface{} { return reflect.New(rt) } 47 | 48 | // get binding info 49 | if bind, ok := i.(IBind); ok { 50 | item.info = bind.GetBindInfo(i) 51 | } 52 | 53 | // get method index 54 | it := iv.Type() 55 | nm := it.NumMethod() 56 | for i := 0; i < nm; i++ { 57 | switch it.Method(i).Name { 58 | case ConstructMethod: 59 | item.cmIdx = i 60 | case InitMethod: 61 | item.imIdx = i 62 | } 63 | } 64 | 65 | // get class name 66 | pkgPath := rt.PkgPath() 67 | 68 | if index := strings.Index(pkgPath, "/"+ControllerWeb); index >= 0 { 69 | pkgPath = pkgPath[index+1:] 70 | } 71 | 72 | if index := strings.Index(pkgPath, "/"+ControllerCmd); index >= 0 { 73 | pkgPath = pkgPath[index+1:] 74 | } 75 | 76 | name := pkgPath + "/" + rt.Name() 77 | 78 | if len(name) > VendorLength && name[:VendorLength] == VendorPrefix { 79 | name = name[VendorLength:] 80 | } 81 | 82 | c.items[name] = &item 83 | } 84 | 85 | // Has check if the class exists in container 86 | func (c *Container) Has(name string) bool { 87 | _, ok := c.items[name] 88 | return ok 89 | } 90 | 91 | // GetInfo get class binding info 92 | func (c *Container) GetInfo(name string) interface{} { 93 | if item, ok := c.items[name]; ok { 94 | return item.info 95 | } 96 | 97 | panic("Container: class not found, " + name) 98 | } 99 | 100 | // GetType get class reflect type 101 | func (c *Container) GetType(name string) reflect.Type { 102 | if item, ok := c.items[name]; ok { 103 | return item.zero.Type() 104 | } 105 | 106 | panic("Container: class not found, " + name) 107 | } 108 | 109 | // Get get new class object. name is class name, config is properties map, 110 | // params is optional construct parameters. 111 | func (c *Container) Get(name string, config map[string]interface{}, params ...interface{}) reflect.Value { 112 | item, ok := c.items[name] 113 | if !ok { 114 | panic("Container: class not found, " + name) 115 | } 116 | 117 | // get new object from pool 118 | rv := item.pool.Get().(reflect.Value) 119 | 120 | if pl := len(params); pl > 0 { 121 | if ctx, ok := params[pl-1].(*Context); ok { 122 | if c.enablePool { 123 | // reset properties 124 | rv.Elem().Set(item.zero) 125 | ctx.cache(name, rv) 126 | } 127 | 128 | if obj, ok := rv.Interface().(IObject); ok { 129 | // inject context 130 | obj.SetContext(ctx) 131 | } 132 | 133 | params = params[:pl-1] 134 | } 135 | } 136 | 137 | // call Construct([arg1, arg2, ...]) 138 | if item.cmIdx != -1 { 139 | if cm := rv.Method(item.cmIdx); cm.IsValid() { 140 | in := make([]reflect.Value, 0) 141 | for _, arg := range params { 142 | in = append(in, reflect.ValueOf(arg)) 143 | } 144 | 145 | cm.Call(in) 146 | } 147 | } 148 | 149 | // configure object 150 | Configure(rv, config) 151 | 152 | // call Init() 153 | if item.imIdx != -1 { 154 | if im := rv.Method(item.imIdx); im.IsValid() { 155 | in := make([]reflect.Value, 0) 156 | im.Call(in) 157 | } 158 | } 159 | 160 | return rv 161 | } 162 | 163 | // Put put back reflect value to object pool 164 | func (c *Container) Put(name string, rv reflect.Value) { 165 | if item, ok := c.items[name]; ok { 166 | item.pool.Put(rv) 167 | return 168 | } 169 | 170 | panic("Container: class not found, " + name) 171 | } 172 | -------------------------------------------------------------------------------- /Client/Redis/Adapter.go: -------------------------------------------------------------------------------- 1 | package Redis 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/pinguo/pgo" 7 | "github.com/pinguo/pgo/Util" 8 | ) 9 | 10 | // Adapter of Redis Client, add context support. 11 | // usage: redis := this.GetObject(Redis.AdapterClass).(*Redis.Adapter) 12 | type Adapter struct { 13 | pgo.Object 14 | client *Client 15 | panicRecover bool 16 | } 17 | 18 | func (a *Adapter) Construct(componentId ...string) { 19 | id := defaultComponentId 20 | if len(componentId) > 0 { 21 | id = componentId[0] 22 | } 23 | 24 | a.client = pgo.App.Get(id).(*Client) 25 | a.panicRecover = true 26 | } 27 | 28 | func (a *Adapter) SetPanicRecover(v bool) { 29 | a.panicRecover = v 30 | } 31 | 32 | func (a *Adapter) GetClient() *Client { 33 | return a.client 34 | } 35 | 36 | func (a *Adapter) handlePanic() { 37 | if a.panicRecover { 38 | if v := recover(); v != nil { 39 | a.GetContext().Error(Util.ToString(v)) 40 | } 41 | } 42 | } 43 | 44 | func (a *Adapter) Get(key string) *pgo.Value { 45 | profile := "Redis.Get" 46 | a.GetContext().ProfileStart(profile) 47 | defer a.GetContext().ProfileStop(profile) 48 | defer a.handlePanic() 49 | 50 | res, hit := a.client.Get(key), 0 51 | if res != nil && res.Valid() { 52 | hit = 1 53 | } 54 | 55 | a.GetContext().Counting(profile, hit, 1) 56 | return res 57 | } 58 | 59 | func (a *Adapter) MGet(keys []string) map[string]*pgo.Value { 60 | profile := "Redis.MGet" 61 | a.GetContext().ProfileStart(profile) 62 | defer a.GetContext().ProfileStop(profile) 63 | defer a.handlePanic() 64 | 65 | res, hit := a.client.MGet(keys), 0 66 | for _, v := range res { 67 | if v != nil && v.Valid() { 68 | hit += 1 69 | } 70 | } 71 | 72 | a.GetContext().Counting(profile, hit, len(keys)) 73 | return res 74 | } 75 | 76 | func (a *Adapter) Set(key string, value interface{}, expire ...time.Duration) bool { 77 | profile := "Redis.Set" 78 | a.GetContext().ProfileStart(profile) 79 | defer a.GetContext().ProfileStop(profile) 80 | defer a.handlePanic() 81 | 82 | return a.client.Set(key, value, expire...) 83 | } 84 | 85 | func (a *Adapter) MSet(items map[string]interface{}, expire ...time.Duration) bool { 86 | profile := "Redis.MSet" 87 | a.GetContext().ProfileStart(profile) 88 | defer a.GetContext().ProfileStop(profile) 89 | defer a.handlePanic() 90 | 91 | return a.client.MSet(items, expire...) 92 | } 93 | 94 | func (a *Adapter) Add(key string, value interface{}, expire ...time.Duration) bool { 95 | profile := "Redis.Add" 96 | a.GetContext().ProfileStart(profile) 97 | defer a.GetContext().ProfileStop(profile) 98 | defer a.handlePanic() 99 | 100 | return a.client.Add(key, value, expire...) 101 | } 102 | 103 | func (a *Adapter) MAdd(items map[string]interface{}, expire ...time.Duration) bool { 104 | profile := "Redis.MAdd" 105 | a.GetContext().ProfileStart(profile) 106 | defer a.GetContext().ProfileStart(profile) 107 | defer a.handlePanic() 108 | 109 | return a.client.MAdd(items, expire...) 110 | } 111 | 112 | func (a *Adapter) Del(key string) bool { 113 | profile := "Redis.Del" 114 | a.GetContext().ProfileStart(profile) 115 | defer a.GetContext().ProfileStop(profile) 116 | defer a.handlePanic() 117 | 118 | return a.client.Del(key) 119 | } 120 | 121 | func (a *Adapter) MDel(keys []string) bool { 122 | profile := "Redis.MDel" 123 | a.GetContext().ProfileStart(profile) 124 | defer a.GetContext().ProfileStop(profile) 125 | defer a.handlePanic() 126 | 127 | return a.client.MDel(keys) 128 | } 129 | 130 | func (a *Adapter) Exists(key string) bool { 131 | profile := "Redis.Exists" 132 | a.GetContext().ProfileStart(profile) 133 | defer a.GetContext().ProfileStop(profile) 134 | defer a.handlePanic() 135 | 136 | return a.client.Exists(key) 137 | } 138 | 139 | func (a *Adapter) Incr(key string, delta int) int { 140 | profile := "Redis.Incr" 141 | a.GetContext().ProfileStart(profile) 142 | defer a.GetContext().ProfileStop(profile) 143 | defer a.handlePanic() 144 | 145 | return a.client.Incr(key, delta) 146 | } 147 | 148 | // 支持的命令请查阅:Redis.allRedisCmd 149 | // args = [0:"key"] 150 | // Example: 151 | // redis := t.GetObject(Redis.AdapterClass).(*Redis.Adapter) 152 | // retI := redis.Do("SADD","myTest", "test1") 153 | // ret := retI.(int) 154 | // fmt.Println(ret) = 1 155 | // retList :=redis.Do("SMEMBERS","myTest") 156 | // retListI,_:=ret.([]interface{}) 157 | // for _,v:=range retListI{ 158 | // vv :=pgo.NewValue(v) // 写入的时候有pgo.Encode(),如果存入的是结构体或slice map 需要decode,其他类型直接断言类型 159 | // fmt.Println(vv.String()) // test1 160 | // } 161 | func (a *Adapter) Do(cmd string, args ...interface{}) interface{} { 162 | profile := "Redis.Do." + cmd 163 | a.GetContext().ProfileStart(profile) 164 | defer a.GetContext().ProfileStop(profile) 165 | defer a.handlePanic() 166 | return a.client.Do(cmd, args ...) 167 | } 168 | -------------------------------------------------------------------------------- /Value.go: -------------------------------------------------------------------------------- 1 | package pgo 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | "strconv" 9 | 10 | "github.com/pinguo/pgo/Util" 11 | ) 12 | 13 | func NewValue(data interface{}) *Value { 14 | return &Value{data} 15 | } 16 | 17 | // Encode encode data to bytes 18 | func Encode(data interface{}) []byte { 19 | v := Value{data} 20 | return v.Encode() 21 | } 22 | 23 | // Decode data bytes to ptr 24 | func Decode(data interface{}, ptr interface{}) { 25 | v := Value{data} 26 | v.Decode(ptr) 27 | } 28 | 29 | // Value adapter for value of any type, 30 | // provide uniform encoding and decoding. 31 | type Value struct { 32 | data interface{} 33 | } 34 | 35 | // Valid check the underlying data is nil 36 | func (v *Value) Valid() bool { 37 | return v.data != nil 38 | } 39 | 40 | // TryEncode try encode data, err is not nil if panic 41 | func (v *Value) TryEncode() (output []byte, err error) { 42 | defer func() { 43 | if v := recover(); v != nil { 44 | output, err = nil, errors.New(Util.ToString(v)) 45 | } 46 | }() 47 | return v.Encode(), nil 48 | } 49 | 50 | // TryDecode try decode data, err is not nil if panic 51 | func (v *Value) TryDecode(ptr interface{}) (err error) { 52 | defer func() { 53 | if v := recover(); v != nil { 54 | err = errors.New(Util.ToString(v)) 55 | } 56 | }() 57 | v.Decode(ptr) 58 | return nil 59 | } 60 | 61 | // Encode encode data to bytes, panic if failed 62 | func (v *Value) Encode() []byte { 63 | var output []byte 64 | switch d := v.data.(type) { 65 | case []byte: 66 | output = d 67 | case string: 68 | output = []byte(d) 69 | case bool: 70 | output = strconv.AppendBool(output, d) 71 | case float32, float64: 72 | f64 := reflect.ValueOf(v.data).Float() 73 | output = strconv.AppendFloat(output, f64, 'g', -1, 64) 74 | case int, int8, int16, int32, int64: 75 | i64 := reflect.ValueOf(v.data).Int() 76 | output = strconv.AppendInt(output, i64, 10) 77 | case uint, uint8, uint16, uint32, uint64: 78 | u64 := reflect.ValueOf(v.data).Uint() 79 | output = strconv.AppendUint(output, u64, 10) 80 | default: 81 | if j, e := json.Marshal(v.data); e == nil { 82 | output = j 83 | } else { 84 | panic("Value.Encode: " + e.Error()) 85 | } 86 | } 87 | return output 88 | } 89 | 90 | // Decode decode data bytes to ptr, panic if failed 91 | func (v *Value) Decode(ptr interface{}) { 92 | switch p := ptr.(type) { 93 | case *[]byte: 94 | *p = v.Bytes() 95 | case *string: 96 | *p = v.String() 97 | case *bool: 98 | *p = Util.ToBool(v.data) 99 | case *float32, *float64: 100 | fv := Util.ToFloat(v.data) 101 | rv := reflect.ValueOf(ptr).Elem() 102 | rv.Set(reflect.ValueOf(fv).Convert(rv.Type())) 103 | case *int, *int8, *int16, *int32, *int64: 104 | iv := Util.ToInt(v.data) 105 | rv := reflect.ValueOf(ptr).Elem() 106 | rv.Set(reflect.ValueOf(iv).Convert(rv.Type())) 107 | case *uint, *uint8, *uint16, *uint32, *uint64: 108 | iv := Util.ToInt(v.data) 109 | rv := reflect.ValueOf(ptr).Elem() 110 | rv.Set(reflect.ValueOf(iv).Convert(rv.Type())) 111 | default: 112 | if e := json.Unmarshal(v.Bytes(), ptr); e != nil { 113 | rv := reflect.ValueOf(ptr) 114 | if rv.Kind() != reflect.Ptr || rv.IsNil() { 115 | panic("Value.Decode: require a valid pointer") 116 | } 117 | 118 | if rv = rv.Elem(); rv.Kind() == reflect.Interface { 119 | rv.Set(reflect.ValueOf(v.data)) 120 | } else { 121 | panic("Value.Decode: " + e.Error()) 122 | } 123 | } 124 | } 125 | } 126 | 127 | // Data return underlying data 128 | func (v *Value) Data() interface{} { 129 | return v.data 130 | } 131 | 132 | // Bool return underlying data as bool 133 | func (v *Value) Bool() bool { 134 | return Util.ToBool(v.data) 135 | } 136 | 137 | // Int return underlying data as int 138 | func (v *Value) Int() int { 139 | return Util.ToInt(v.data) 140 | } 141 | 142 | // Float return underlying data as float64 143 | func (v *Value) Float() float64 { 144 | return Util.ToFloat(v.data) 145 | } 146 | 147 | // String return underlying data as string 148 | func (v *Value) String() string { 149 | switch d := v.data.(type) { 150 | case []byte: 151 | return string(d) 152 | case string: 153 | return d 154 | default: 155 | if j, e := json.Marshal(v.data); e == nil { 156 | return string(j) 157 | } 158 | return fmt.Sprintf("%+v", v.data) 159 | } 160 | } 161 | 162 | // Bytes return underlying data as bytes 163 | func (v *Value) Bytes() []byte { 164 | switch d := v.data.(type) { 165 | case []byte: 166 | return d 167 | case string: 168 | return []byte(d) 169 | default: 170 | if j, e := json.Marshal(v.data); e == nil { 171 | return j 172 | } 173 | return []byte(fmt.Sprintf("%+v", v.data)) 174 | } 175 | } 176 | 177 | func (v *Value) MarshalJSON() ([]byte, error) { 178 | return json.Marshal(v.String()) 179 | } 180 | 181 | func (v *Value) UnmarshalJSON(b []byte) error { 182 | var s string 183 | if e := json.Unmarshal(b, &s); e != nil { 184 | return e 185 | } 186 | v.data = s 187 | return nil 188 | } 189 | -------------------------------------------------------------------------------- /Client/Memcache/Adapter.go: -------------------------------------------------------------------------------- 1 | package Memcache 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/pinguo/pgo" 7 | "github.com/pinguo/pgo/Util" 8 | ) 9 | 10 | // Adapter of Memcache Client, add context support. 11 | // usage: mc := this.GetObject(Memcache.AdapterClass.(*Memcache.Adapter) 12 | type Adapter struct { 13 | pgo.Object 14 | client *Client 15 | panicRecover bool 16 | } 17 | 18 | func (a *Adapter) Construct(componentId ...string) { 19 | id := defaultComponentId 20 | if len(componentId) > 0 { 21 | id = componentId[0] 22 | } 23 | 24 | a.client = pgo.App.Get(id).(*Client) 25 | a.panicRecover = true 26 | } 27 | 28 | func (a *Adapter) SetPanicRecover(v bool) { 29 | a.panicRecover = v 30 | } 31 | 32 | func (a *Adapter) GetClient() *Client { 33 | return a.client 34 | } 35 | 36 | func (a *Adapter) handlePanic() { 37 | if a.panicRecover { 38 | if v := recover(); v != nil { 39 | a.GetContext().Error(Util.ToString(v)) 40 | } 41 | } 42 | } 43 | 44 | func (a *Adapter) Get(key string) *pgo.Value { 45 | profile := "Memcache.Get" 46 | a.GetContext().ProfileStart(profile) 47 | defer a.GetContext().ProfileStop(profile) 48 | defer a.handlePanic() 49 | 50 | res, hit := a.client.Get(key), 0 51 | if res != nil && res.Valid() { 52 | hit = 1 53 | } 54 | 55 | a.GetContext().Counting(profile, hit, 1) 56 | return res 57 | } 58 | 59 | func (a *Adapter) MGet(keys []string) map[string]*pgo.Value { 60 | profile := "Memcache.MGet" 61 | a.GetContext().ProfileStart(profile) 62 | defer a.GetContext().ProfileStop(profile) 63 | defer a.handlePanic() 64 | 65 | res, hit := a.client.MGet(keys), 0 66 | for _, v := range res { 67 | if v != nil && v.Valid() { 68 | hit += 1 69 | } 70 | } 71 | 72 | a.GetContext().Counting(profile, hit, len(keys)) 73 | return res 74 | } 75 | 76 | func (a *Adapter) Set(key string, value interface{}, expire ...time.Duration) bool { 77 | profile := "Memcache.Set" 78 | a.GetContext().ProfileStart(profile) 79 | defer a.GetContext().ProfileStop(profile) 80 | defer a.handlePanic() 81 | 82 | return a.client.Set(key, value, expire...) 83 | } 84 | 85 | func (a *Adapter) MSet(items map[string]interface{}, expire ...time.Duration) bool { 86 | profile := "Memcache.MSet" 87 | a.GetContext().ProfileStart(profile) 88 | defer a.GetContext().ProfileStop(profile) 89 | defer a.handlePanic() 90 | 91 | return a.client.MSet(items, expire...) 92 | } 93 | 94 | func (a *Adapter) Add(key string, value interface{}, expire ...time.Duration) bool { 95 | profile := "Memcache.Add" 96 | a.GetContext().ProfileStart(profile) 97 | defer a.GetContext().ProfileStop(profile) 98 | defer a.handlePanic() 99 | 100 | return a.client.Add(key, value, expire...) 101 | } 102 | 103 | func (a *Adapter) MAdd(items map[string]interface{}, expire ...time.Duration) bool { 104 | profile := "Memcache.MAdd" 105 | a.GetContext().ProfileStart(profile) 106 | defer a.GetContext().ProfileStart(profile) 107 | defer a.handlePanic() 108 | 109 | return a.client.MAdd(items, expire...) 110 | } 111 | 112 | func (a *Adapter) Del(key string) bool { 113 | profile := "Memcache.Del" 114 | a.GetContext().ProfileStart(profile) 115 | defer a.GetContext().ProfileStop(profile) 116 | defer a.handlePanic() 117 | 118 | return a.client.Del(key) 119 | } 120 | 121 | func (a *Adapter) MDel(keys []string) bool { 122 | profile := "Memcache.MDel" 123 | a.GetContext().ProfileStart(profile) 124 | defer a.GetContext().ProfileStop(profile) 125 | defer a.handlePanic() 126 | 127 | return a.client.MDel(keys) 128 | } 129 | 130 | func (a *Adapter) Exists(key string) bool { 131 | profile := "Memcache.Exists" 132 | a.GetContext().ProfileStart(profile) 133 | defer a.GetContext().ProfileStop(profile) 134 | defer a.handlePanic() 135 | 136 | return a.client.Exists(key) 137 | } 138 | 139 | func (a *Adapter) Incr(key string, delta int) int { 140 | profile := "Memcache.Incr" 141 | a.GetContext().ProfileStart(profile) 142 | defer a.GetContext().ProfileStop(profile) 143 | defer a.handlePanic() 144 | 145 | return a.client.Incr(key, delta) 146 | } 147 | 148 | func (a *Adapter) Retrieve(cmd, key string) *Item { 149 | profile := "Memcache.Retrieve" 150 | a.GetContext().ProfileStart(profile) 151 | defer a.GetContext().ProfileStop(profile) 152 | defer a.handlePanic() 153 | 154 | return a.client.Retrieve(cmd, key) 155 | } 156 | 157 | func (a *Adapter) MultiRetrieve(cmd string, keys []string) []*Item { 158 | profile := "Memcache.MultiRetrieve" 159 | a.GetContext().ProfileStart(profile) 160 | defer a.GetContext().ProfileStop(profile) 161 | defer a.handlePanic() 162 | 163 | return a.client.MultiRetrieve(cmd, keys) 164 | } 165 | 166 | func (a *Adapter) Store(cmd string, item *Item, expire ...time.Duration) bool { 167 | profile := "Memcache.Store" 168 | a.GetContext().ProfileStart(profile) 169 | defer a.GetContext().ProfileStop(profile) 170 | defer a.handlePanic() 171 | 172 | return a.client.Store(cmd, item, expire...) 173 | } 174 | 175 | func (a *Adapter) MultiStore(cmd string, items []*Item, expire ...time.Duration) bool { 176 | profile := "Memcache.MultiStore" 177 | a.GetContext().ProfileStart(profile) 178 | defer a.GetContext().ProfileStop(profile) 179 | defer a.handlePanic() 180 | 181 | return a.client.MultiStore(cmd, items, expire...) 182 | } 183 | -------------------------------------------------------------------------------- /Client/RabbitMq/Client.go: -------------------------------------------------------------------------------- 1 | package RabbitMq 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "time" 7 | 8 | "github.com/streadway/amqp" 9 | ) 10 | 11 | // RabbitMq client component, 12 | // support Publisher-Consumer configuration: 13 | // rabbitMq: 14 | // class: "@pgo/Client/RabbitMq/Client" 15 | // tlsRootCAs:"" 16 | // tlsCert: "" 17 | // tlsCertKey: "" 18 | // user: "guest" 19 | // pass: "guest" 20 | // exchangeName: "" 21 | // exchangeType: "" 22 | // maxChannelNum: 2000 23 | // maxIdleChannel: "200" 24 | // maxIdleChannelTime:"10s" 25 | // probeInterval: "0s" 26 | // maxWaitTime: "200ms" 27 | // serverName: "pgo-xxx" 28 | // servers: 29 | // - "127.0.0.1:6379" 30 | // - "127.0.0.1:6380" 31 | type Client struct { 32 | Pool 33 | } 34 | 35 | func (c *Client) decodeBody(d amqp.Delivery, ret interface{}) error { 36 | var network *bytes.Buffer 37 | network = bytes.NewBuffer(d.Body) 38 | dec := gob.NewDecoder(network) 39 | err := dec.Decode(ret) 40 | 41 | return err 42 | } 43 | 44 | func (c *Client) decodeHeaders(d amqp.Delivery) *RabbitHeaders { 45 | ret := &RabbitHeaders{ 46 | Exchange: d.Exchange, 47 | RouteKey: d.RoutingKey, 48 | Timestamp: d.Timestamp, 49 | MessageId: d.MessageId, 50 | } 51 | 52 | for k, iV := range d.Headers { 53 | v, _ := iV.(string) 54 | switch k { 55 | case "logId": 56 | ret.LogId = v 57 | case "service": 58 | ret.Service = v 59 | case "opUid": 60 | ret.OpUid = v 61 | } 62 | } 63 | 64 | return ret 65 | } 66 | 67 | func (c *Client) setExchangeDeclare() { 68 | ch := c.getFreeChannel() 69 | defer ch.Close(false) 70 | c.exchangeDeclare(ch) 71 | } 72 | 73 | func (c *Client) publish(parameter *PublishData, logId string) bool{ 74 | if parameter.OpCode == "" || parameter.Data == nil { 75 | panic("Rabbit OpCode and LogId cannot be empty") 76 | } 77 | 78 | ch := c.getFreeChannel() 79 | defer ch.Close(false) 80 | 81 | // 增加速度,在消费端定义交换机 或者单独定义交换机 82 | // c.exchangeDeclare(ch) 83 | 84 | var goBytes bytes.Buffer 85 | myGob := gob.NewEncoder(&goBytes) 86 | err := myGob.Encode(parameter.Data) 87 | c.failOnError(err, "Encode err") 88 | 89 | err = ch.channel.Publish( 90 | c.getExchangeName(), // exchange 91 | c.getRouteKey(parameter.OpCode), // routing key 92 | false, // mandatory 93 | false, // immediate 94 | amqp.Publishing{ 95 | ContentType: "text/plain", 96 | Body: goBytes.Bytes(), 97 | Headers: amqp.Table{"logId": logId, "service": c.ServiceName, "opUid": parameter.OpUid}, 98 | Timestamp: time.Now(), 99 | }) 100 | c.failOnError(err, "Failed to publish a message") 101 | return true 102 | } 103 | 104 | // 定义交换机 105 | func (c *Client) exchangeDeclare(ch *ChannelBox) bool { 106 | err := ch.channel.ExchangeDeclare( 107 | c.getExchangeName(), // name 108 | c.exchangeType, // type 109 | true, // durable 110 | false, // auto-deleted 111 | false, // internal 112 | false, // no-wait 113 | nil, // arguments 114 | ) 115 | c.failOnError(err, "Failed to declare an exchange") 116 | 117 | return true 118 | } 119 | 120 | // 定义交换机 121 | func (c *Client) bindQueue(ch *ChannelBox, queueName string, opCodes []string) bool { 122 | for _, opCode := range opCodes { 123 | err := ch.channel.QueueBind( 124 | queueName, // queue name 125 | c.getRouteKey(opCode), // routing key 126 | c.getExchangeName(), // exchange 127 | false, 128 | nil) 129 | 130 | c.failOnError(err, "Failed to bind a queue") 131 | } 132 | 133 | return true 134 | } 135 | 136 | func (c *Client) queueDeclare(ch *ChannelBox, queueName string) amqp.Queue { 137 | q, err := ch.channel.QueueDeclare( 138 | queueName, // name 139 | true, // durable 140 | false, // delete when usused 141 | false, // exclusive 142 | false, // no-wait 143 | nil, // arguments 144 | ) 145 | 146 | if err != nil { 147 | c.failOnError(err, "Failed to declare a queue") 148 | } 149 | 150 | return q 151 | } 152 | 153 | func (c *Client) getConsumeChannelBox(queueName string, opCodes []string) *ChannelBox { 154 | ch := c.getFreeChannel() 155 | // 定义交换机 156 | c.exchangeDeclare(ch) 157 | // 定义queue 158 | c.queueDeclare(ch, queueName) 159 | // 绑定queue 160 | c.bindQueue(ch, queueName, opCodes) 161 | 162 | return ch 163 | 164 | } 165 | 166 | func (c *Client) consume(parameter *ConsumeData) <-chan amqp.Delivery { 167 | ch := c.getConsumeChannelBox(parameter.QueueName, parameter.OpCodes) 168 | // defer ch.Close(false) 169 | err := ch.channel.Qos(parameter.Limit, 0, false) 170 | c.failOnError(err, "set Qos err") 171 | 172 | messages, err := ch.channel.Consume( 173 | parameter.QueueName, // queue 174 | "", // consumer 175 | parameter.AutoAck, // auto ack 176 | parameter.Exclusive, // exclusive 177 | false, // no local 178 | parameter.NoWait, // no wait 179 | nil, // args 180 | ) 181 | c.failOnError(err, "get msg err") 182 | 183 | return messages 184 | } 185 | 186 | func (c *Client) failOnError(err error, msg string) { 187 | if err != nil { 188 | panic("Rabbit:" + msg + ",err:" + err.Error()) 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /Client/Memcache/Client.go: -------------------------------------------------------------------------------- 1 | package Memcache 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "time" 7 | 8 | "github.com/pinguo/pgo" 9 | ) 10 | 11 | // Memcache Client component, configuration: 12 | // memcache: 13 | // class: "@pgo/Client/Memcache/Client" 14 | // prefix: "pgo_" 15 | // maxIdleConn: 10 16 | // maxIdleTime: "60s" 17 | // netTimeout: "1s" 18 | // probInterval: "0s" 19 | // servers: 20 | // - "127.0.0.1:11211" 21 | // - "127.0.0.1:11212" 22 | type Client struct { 23 | Pool 24 | } 25 | 26 | func (c *Client) Get(key string) *pgo.Value { 27 | if item := c.Retrieve(CmdGet, key); item != nil { 28 | return pgo.NewValue(item.Data) 29 | } 30 | return pgo.NewValue(nil) 31 | } 32 | 33 | func (c *Client) MGet(keys []string) map[string]*pgo.Value { 34 | result := make(map[string]*pgo.Value) 35 | for _, key := range keys { 36 | result[key] = pgo.NewValue(nil) 37 | } 38 | 39 | if items := c.MultiRetrieve(CmdGet, keys); len(items) > 0 { 40 | for _, item := range items { 41 | result[item.Key] = pgo.NewValue(item.Data) 42 | } 43 | } 44 | return result 45 | } 46 | 47 | func (c *Client) Set(key string, value interface{}, expire ...time.Duration) bool { 48 | return c.Store(CmdSet, &Item{Key: key, Data: pgo.Encode(value)}, expire...) 49 | } 50 | 51 | func (c *Client) MSet(items map[string]interface{}, expire ...time.Duration) bool { 52 | newItems := make([]*Item, 0, len(items)) 53 | for key, value := range items { 54 | newItems = append(newItems, &Item{Key: key, Data: pgo.Encode(value)}) 55 | } 56 | return c.MultiStore(CmdSet, newItems, expire...) 57 | } 58 | 59 | func (c *Client) Add(key string, value interface{}, expire ...time.Duration) bool { 60 | return c.Store(CmdAdd, &Item{Key: key, Data: pgo.Encode(value)}, expire...) 61 | } 62 | 63 | func (c *Client) MAdd(items map[string]interface{}, expire ...time.Duration) bool { 64 | newItems := make([]*Item, 0, len(items)) 65 | for key, value := range items { 66 | newItems = append(newItems, &Item{Key: key, Data: pgo.Encode(value)}) 67 | } 68 | return c.MultiStore(CmdAdd, newItems, expire...) 69 | } 70 | 71 | func (c *Client) Del(key string) bool { 72 | newKey := c.BuildKey(key) 73 | conn := c.GetConnByKey(newKey) 74 | defer conn.Close(false) 75 | 76 | return conn.Delete(newKey) 77 | } 78 | 79 | func (c *Client) MDel(keys []string) bool { 80 | addrKeys, _ := c.AddrNewKeys(keys) 81 | wg, success := new(sync.WaitGroup), uint32(0) 82 | 83 | wg.Add(len(addrKeys)) 84 | for addr, keys := range addrKeys { 85 | go c.RunAddrFunc(addr, keys, wg, func(conn *Conn, keys []string) { 86 | for _, key := range keys { 87 | // extend deadline for every operation 88 | conn.ExtendDeadLine() 89 | if ok := conn.Delete(key); ok { 90 | atomic.AddUint32(&success, 1) 91 | } 92 | } 93 | }) 94 | } 95 | 96 | wg.Wait() 97 | return success == uint32(len(keys)) 98 | } 99 | 100 | func (c *Client) Exists(key string) bool { 101 | return c.Get(key) != nil 102 | } 103 | 104 | func (c *Client) Incr(key string, delta int) int { 105 | newKey := c.BuildKey(key) 106 | conn := c.GetConnByKey(newKey) 107 | defer conn.Close(false) 108 | 109 | return conn.Increment(newKey, delta) 110 | } 111 | 112 | func (c *Client) Retrieve(cmd, key string) *Item { 113 | newKey := c.BuildKey(key) 114 | conn := c.GetConnByKey(newKey) 115 | defer conn.Close(false) 116 | 117 | if items := conn.Retrieve(cmd, newKey); len(items) == 1 { 118 | return items[0] 119 | } 120 | return nil 121 | } 122 | 123 | func (c *Client) MultiRetrieve(cmd string, keys []string) []*Item { 124 | result := make([]*Item, 0, len(keys)) 125 | addrKeys, newKeys := c.AddrNewKeys(keys) 126 | lock, wg := new(sync.Mutex), new(sync.WaitGroup) 127 | 128 | wg.Add(len(addrKeys)) 129 | for addr, keys := range addrKeys { 130 | go c.RunAddrFunc(addr, keys, wg, func(conn *Conn, keys []string) { 131 | if items := conn.Retrieve(cmd, keys...); len(items) > 0 { 132 | lock.Lock() 133 | defer lock.Unlock() 134 | for _, item := range items { 135 | item.Key = newKeys[item.Key] 136 | result = append(result, item) 137 | } 138 | } 139 | }) 140 | } 141 | 142 | wg.Wait() 143 | return result 144 | } 145 | 146 | func (c *Client) Store(cmd string, item *Item, expire ...time.Duration) bool { 147 | item.Key = c.BuildKey(item.Key) 148 | conn := c.GetConnByKey(item.Key) 149 | defer conn.Close(false) 150 | 151 | expire = append(expire, defaultExpire) 152 | return conn.Store(cmd, item, int(expire[0]/time.Second)) 153 | } 154 | 155 | func (c *Client) MultiStore(cmd string, items []*Item, expire ...time.Duration) bool { 156 | expire = append(expire, defaultExpire) 157 | addrItems := make(map[string][]*Item) 158 | wg, success := new(sync.WaitGroup), uint32(0) 159 | 160 | for _, item := range items { 161 | item.Key = c.BuildKey(item.Key) 162 | addr := c.GetAddrByKey(item.Key) 163 | addrItems[addr] = append(addrItems[addr], item) 164 | } 165 | 166 | wg.Add(len(addrItems)) 167 | for addr := range addrItems { 168 | go c.RunAddrFunc(addr, nil, wg, func(conn *Conn, keys []string) { 169 | for _, item := range addrItems[addr] { 170 | conn.ExtendDeadLine() // extend deadline for every store 171 | if ok := conn.Store(cmd, item, int(expire[0]/time.Second)); ok { 172 | atomic.AddUint32(&success, 1) 173 | } 174 | } 175 | }) 176 | } 177 | 178 | wg.Wait() 179 | return success == uint32(len(items)) 180 | } 181 | -------------------------------------------------------------------------------- /Client/Http/Client.go: -------------------------------------------------------------------------------- 1 | package Http 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/tls" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | "net/url" 11 | "reflect" 12 | "strings" 13 | "time" 14 | 15 | "github.com/pinguo/pgo" 16 | "github.com/pinguo/pgo/Util" 17 | ) 18 | 19 | // Http Client component, configuration: 20 | // http: 21 | // class: "@pgo/Client/Http/Client" 22 | // verifyPeer: false 23 | // userAgent: "PGO Framework" 24 | // timeout: "10s" 25 | type Client struct { 26 | verifyPeer bool // verify https peer or not 27 | userAgent string // default User-Agent header 28 | timeout time.Duration // default request timeout 29 | 30 | client *http.Client 31 | } 32 | 33 | func (c *Client) Construct() { 34 | c.verifyPeer = false 35 | c.userAgent = defaultUserAgent 36 | c.timeout = defaultTimeout 37 | } 38 | 39 | func (c *Client) Init() { 40 | // reused client and transport, transport will cache 41 | // connections for future reuse, if transport created 42 | // on demand, net poll goroutine on connection per 43 | // transport will be leaked. 44 | c.client = &http.Client{ 45 | Transport: &http.Transport{ 46 | TLSClientConfig: &tls.Config{ 47 | InsecureSkipVerify: !c.verifyPeer, 48 | }, 49 | }, 50 | } 51 | } 52 | 53 | func (c *Client) SetVerifyPeer(verifyPeer bool) { 54 | c.verifyPeer = verifyPeer 55 | } 56 | 57 | func (c *Client) SetUserAgent(userAgent string) { 58 | c.userAgent = userAgent 59 | } 60 | 61 | func (c *Client) SetTimeout(v string) { 62 | if timeout, err := time.ParseDuration(v); err != nil { 63 | panic("http parse timeout failed, " + err.Error()) 64 | } else { 65 | c.timeout = timeout 66 | } 67 | } 68 | 69 | // Get perform a get request, and return a response pointer. 70 | // addr is the request url. data is the params associated 71 | // and will be append to addr if not empty, data type can be 72 | // url.Values or map with string key. option is an optional 73 | // configuration object to specify header, cookie etc. 74 | func (c *Client) Get(addr string, data interface{}, option ...*Option) *http.Response { 75 | var query url.Values 76 | switch v := data.(type) { 77 | case nil: 78 | // pass 79 | 80 | case url.Values: 81 | query = v 82 | 83 | case map[string]interface{}, map[string]string, pgo.Map: 84 | query = make(url.Values) 85 | rv := reflect.ValueOf(data) 86 | keys := rv.MapKeys() 87 | for _, key := range keys { 88 | val := rv.MapIndex(key) 89 | query.Set(key.String(), Util.ToString(val.Interface())) 90 | } 91 | 92 | default: 93 | panic(fmt.Sprintf("http get invalid data type: %T", data)) 94 | } 95 | 96 | if len(query) != 0 { 97 | if pos := strings.IndexByte(addr, '?'); pos == -1 { 98 | addr = addr + "?" + query.Encode() 99 | } else { 100 | addr = addr + "&" + query.Encode() 101 | } 102 | } 103 | 104 | req, err := http.NewRequest("GET", addr, nil) 105 | if err != nil { 106 | panic("http get bad request, " + err.Error()) 107 | } 108 | 109 | return c.Do(req, option...) 110 | } 111 | 112 | // Post perform a post request, and return a response pointer. 113 | // addr is the request url. data is the params will be sent, 114 | // data type can be url.Values, map with string key, string, 115 | // []byte or io.Reader, if url.Values or map is specified, 116 | // Content-Type header will be set to "application/x-www-form-urlencoded". 117 | // option is an optional configuration object to specify header, cookie etc. 118 | func (c *Client) Post(addr string, data interface{}, option ...*Option) *http.Response { 119 | var body io.Reader 120 | var contentType string 121 | 122 | switch v := data.(type) { 123 | case nil: 124 | // pass 125 | 126 | case url.Values: 127 | body = strings.NewReader(v.Encode()) 128 | contentType = "application/x-www-form-urlencoded" 129 | 130 | case map[string]interface{}, map[string]string, pgo.Map: 131 | query, rv := make(url.Values), reflect.ValueOf(data) 132 | keys := rv.MapKeys() 133 | for _, key := range keys { 134 | val := rv.MapIndex(key) 135 | query.Set(key.String(), Util.ToString(val.Interface())) 136 | } 137 | 138 | body = strings.NewReader(query.Encode()) 139 | contentType = "application/x-www-form-urlencoded" 140 | 141 | case string: 142 | body = strings.NewReader(v) 143 | 144 | case []byte: 145 | body = bytes.NewReader(v) 146 | 147 | case io.Reader: 148 | body = v 149 | 150 | default: 151 | panic(fmt.Sprintf("http post invalid data type: %T", data)) 152 | } 153 | 154 | req, err := http.NewRequest("POST", addr, body) 155 | if err != nil { 156 | panic("http post bad request, " + err.Error()) 157 | } 158 | 159 | if contentType != "" { 160 | req.Header.Set("Content-Type", contentType) 161 | } 162 | 163 | return c.Do(req, option...) 164 | } 165 | 166 | // Do perform a request specified by req param, and return response pointer. 167 | func (c *Client) Do(req *http.Request, option ...*Option) *http.Response { 168 | if c.userAgent != "" { 169 | req.Header.Set("User-Agent", c.userAgent) 170 | } 171 | 172 | timeout := c.timeout 173 | if len(option) > 0 && option[0] != nil { 174 | opt := option[0] 175 | if opt.Timeout > 0 { 176 | timeout = opt.Timeout 177 | } 178 | 179 | for key, val := range opt.Header { 180 | if len(val) > 0 { 181 | req.Header.Set(key, val[0]) 182 | } 183 | } 184 | 185 | for _, cookie := range opt.Cookies { 186 | req.AddCookie(cookie) 187 | } 188 | } 189 | 190 | ctx, _ := context.WithTimeout(req.Context(), timeout) 191 | res, err := c.client.Do(req.WithContext(ctx)) 192 | if err != nil { 193 | panic("http request failed, " + err.Error()) 194 | } 195 | 196 | return res 197 | } 198 | -------------------------------------------------------------------------------- /Client/Memory/Client.go: -------------------------------------------------------------------------------- 1 | package Memory 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | 8 | "github.com/pinguo/pgo" 9 | "github.com/pinguo/pgo/Util" 10 | ) 11 | 12 | type item struct { 13 | value interface{} 14 | expire time.Time 15 | } 16 | 17 | func (i item) isExpired() bool { 18 | return !i.expire.IsZero() && time.Since(i.expire) > 0 19 | } 20 | 21 | // Memory Client component, configuration: 22 | // memory: 23 | // class: "@pgo/Client/Memory/Client" 24 | // gcInterval: "60s" 25 | // gcMaxItems: 1000 26 | type Client struct { 27 | lock sync.RWMutex 28 | items map[string]*item 29 | gcInterval time.Duration 30 | gcMaxItems int 31 | } 32 | 33 | func (c *Client) Construct() { 34 | c.items = make(map[string]*item) 35 | c.gcInterval = defaultGcInterval 36 | c.gcMaxItems = defaultGcMaxItems 37 | } 38 | 39 | func (c *Client) Init() { 40 | go c.gcLoop() 41 | } 42 | 43 | func (c *Client) SetGcInterval(v string) { 44 | if gcInterval, e := time.ParseDuration(v); e != nil { 45 | panic(fmt.Sprintf(errSetProp, "gcInterval", e.Error())) 46 | } else { 47 | c.gcInterval = gcInterval 48 | } 49 | } 50 | 51 | func (c *Client) SetGcMaxItems(gcMaxItems int) { 52 | if gcMaxItems > 0 { 53 | c.gcMaxItems = gcMaxItems 54 | } 55 | } 56 | 57 | func (c *Client) Get(key string) *pgo.Value { 58 | c.lock.RLock() 59 | defer c.lock.RUnlock() 60 | 61 | if item := c.items[key]; item != nil && !item.isExpired() { 62 | return pgo.NewValue(item.value) 63 | } 64 | 65 | return pgo.NewValue(nil) 66 | } 67 | 68 | func (c *Client) MGet(keys []string) map[string]*pgo.Value { 69 | c.lock.RLock() 70 | defer c.lock.RUnlock() 71 | 72 | result := make(map[string]*pgo.Value) 73 | for _, key := range keys { 74 | if item := c.items[key]; item != nil && !item.isExpired() { 75 | result[key] = pgo.NewValue(item.value) 76 | } else { 77 | result[key] = pgo.NewValue(nil) 78 | } 79 | } 80 | 81 | return result 82 | } 83 | 84 | func (c *Client) Set(key string, value interface{}, expire ...time.Duration) bool { 85 | c.lock.Lock() 86 | defer c.lock.Unlock() 87 | 88 | expire, now := append(expire, defaultExpire), time.Now() 89 | c.items[key] = &item{ 90 | value: value, 91 | expire: now.Add(expire[0]), 92 | } 93 | 94 | return true 95 | } 96 | 97 | func (c *Client) MSet(items map[string]interface{}, expire ...time.Duration) bool { 98 | c.lock.Lock() 99 | defer c.lock.Unlock() 100 | 101 | expire, now := append(expire, defaultExpire), time.Now() 102 | for key, value := range items { 103 | c.items[key] = &item{ 104 | value: value, 105 | expire: now.Add(expire[0]), 106 | } 107 | } 108 | return true 109 | } 110 | 111 | func (c *Client) Add(key string, value interface{}, expire ...time.Duration) bool { 112 | c.lock.Lock() 113 | defer c.lock.Unlock() 114 | 115 | expire, now := append(expire, defaultExpire), time.Now() 116 | if old := c.items[key]; old == nil || old.isExpired() { 117 | c.items[key] = &item{ 118 | value: value, 119 | expire: now.Add(expire[0]), 120 | } 121 | return true 122 | } 123 | return false 124 | } 125 | 126 | func (c *Client) MAdd(items map[string]interface{}, expire ...time.Duration) bool { 127 | c.lock.Lock() 128 | defer c.lock.Unlock() 129 | 130 | expire, now, success := append(expire, defaultExpire), time.Now(), 0 131 | for key, value := range items { 132 | if old := c.items[key]; old == nil || old.isExpired() { 133 | c.items[key] = &item{ 134 | value: value, 135 | expire: now.Add(expire[0]), 136 | } 137 | success++ 138 | } 139 | } 140 | 141 | return success == len(items) 142 | } 143 | 144 | func (c *Client) Del(key string) bool { 145 | c.lock.Lock() 146 | defer c.lock.Unlock() 147 | 148 | if _, ok := c.items[key]; ok { 149 | delete(c.items, key) 150 | return true 151 | } 152 | return false 153 | } 154 | 155 | func (c *Client) MDel(keys []string) bool { 156 | c.lock.Lock() 157 | defer c.lock.Unlock() 158 | 159 | success := 0 160 | for _, key := range keys { 161 | if _, ok := c.items[key]; ok { 162 | delete(c.items, key) 163 | success++ 164 | } 165 | } 166 | 167 | return success == len(keys) 168 | } 169 | 170 | func (c *Client) Exists(key string) bool { 171 | c.lock.RLock() 172 | defer c.lock.RUnlock() 173 | 174 | _, ok := c.items[key] 175 | return ok 176 | } 177 | 178 | func (c *Client) Incr(key string, delta int) int { 179 | c.lock.Lock() 180 | defer c.lock.Unlock() 181 | 182 | cur := c.items[key] 183 | if cur == nil { 184 | cur = &item{value: 0} 185 | c.items[key] = cur 186 | } 187 | 188 | newVal := Util.ToInt(cur.value) + delta 189 | cur.value = newVal 190 | return newVal 191 | } 192 | 193 | func (c *Client) gcLoop() { 194 | if c.gcInterval < minGcInterval || c.gcInterval > maxGcInterval { 195 | c.gcInterval = defaultGcInterval 196 | } 197 | 198 | getExpireKeys := func() []string { 199 | c.lock.RLock() 200 | defer c.lock.RUnlock() 201 | 202 | keys, now := make([]string, 0), time.Now() 203 | for key, item := range c.items { 204 | if !item.expire.IsZero() && item.expire.Sub(now) < 0 { 205 | keys = append(keys, key) 206 | if len(keys) >= c.gcMaxItems { 207 | break 208 | } 209 | } 210 | } 211 | return keys 212 | } 213 | 214 | clearExpiredKeys := func(keys []string) { 215 | c.lock.Lock() 216 | defer c.lock.Unlock() 217 | 218 | for _, key := range keys { 219 | delete(c.items, key) 220 | } 221 | } 222 | 223 | for { 224 | <-time.After(c.gcInterval) 225 | if expiredKeys := getExpireKeys(); len(expiredKeys) > 0 { 226 | clearExpiredKeys(expiredKeys) 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /Client/Redis/Client.go: -------------------------------------------------------------------------------- 1 | package Redis 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "sync" 7 | "sync/atomic" 8 | "time" 9 | 10 | "github.com/pinguo/pgo" 11 | "github.com/pinguo/pgo/Util" 12 | ) 13 | 14 | // Redis Client component, require redis-server 2.6.12+ 15 | // configuration: 16 | // redis: 17 | // class: "@pgo/Client/Redis/Client" 18 | // prefix: "pgo_" 19 | // password: "" 20 | // db: 0 21 | // maxIdleConn: 10 22 | // maxIdleTime: "60s" 23 | // netTimeout: "1s" 24 | // probInterval: "0s" 25 | // mod:"cluster" 26 | // servers: 27 | // - "127.0.0.1:6379" 28 | // - "127.0.0.1:6380" 29 | type Client struct { 30 | Pool 31 | } 32 | 33 | func (c *Client) Get(key string) *pgo.Value { 34 | newKey := c.BuildKey(key) 35 | conn := c.GetConnByKey("GET", newKey) 36 | defer conn.Close(false) 37 | 38 | return pgo.NewValue(conn.Do("GET", newKey)) 39 | } 40 | 41 | func (c *Client) MGet(keys []string) map[string]*pgo.Value { 42 | result := make(map[string]*pgo.Value) 43 | addrKeys, newKeys := c.AddrNewKeys("MGET", keys) 44 | lock, wg := new(sync.Mutex), new(sync.WaitGroup) 45 | 46 | wg.Add(len(addrKeys)) 47 | for addr, keys := range addrKeys { 48 | go c.RunAddrFunc(addr, keys, wg, func(conn *Conn, keys []string) { 49 | if items, ok := conn.Do("MGET", keys2Args(keys)...).([]interface{}); ok { 50 | lock.Lock() 51 | defer lock.Unlock() 52 | for i, item := range items { 53 | oldKey := newKeys[keys[i]] 54 | result[oldKey] = pgo.NewValue(item) 55 | } 56 | } 57 | }) 58 | } 59 | 60 | wg.Wait() 61 | return result 62 | } 63 | 64 | func (c *Client) Set(key string, value interface{}, expire ...time.Duration) bool { 65 | expire = append(expire, defaultExpire) 66 | return c.set(key, value, expire[0], "") 67 | } 68 | 69 | func (c *Client) MSet(items map[string]interface{}, expire ...time.Duration) bool { 70 | expire = append(expire, defaultExpire) 71 | return c.mset(items, expire[0], "") 72 | } 73 | 74 | func (c *Client) Add(key string, value interface{}, expire ...time.Duration) bool { 75 | expire = append(expire, defaultExpire) 76 | return c.set(key, value, expire[0], "NX") 77 | } 78 | 79 | func (c *Client) MAdd(items map[string]interface{}, expire ...time.Duration) bool { 80 | expire = append(expire, defaultExpire) 81 | return c.mset(items, expire[0], "NX") 82 | } 83 | 84 | func (c *Client) Del(key string) bool { 85 | newKey := c.BuildKey(key) 86 | conn := c.GetConnByKey("DEL", newKey) 87 | defer conn.Close(false) 88 | 89 | num, ok := conn.Do("DEL", newKey).(int) 90 | return ok && num == 1 91 | } 92 | 93 | func (c *Client) MDel(keys []string) bool { 94 | addrKeys, _ := c.AddrNewKeys("DEL", keys) 95 | wg, success := new(sync.WaitGroup), uint32(0) 96 | 97 | wg.Add(len(addrKeys)) 98 | for addr, keys := range addrKeys { 99 | go c.RunAddrFunc(addr, keys, wg, func(conn *Conn, keys []string) { 100 | if num, ok := conn.Do("DEL", keys2Args(keys)...).(int); ok && num > 0 { 101 | atomic.AddUint32(&success, uint32(num)) 102 | } 103 | }) 104 | } 105 | 106 | wg.Wait() 107 | return success == uint32(len(keys)) 108 | } 109 | 110 | func (c *Client) Exists(key string) bool { 111 | newKey := c.BuildKey(key) 112 | conn := c.GetConnByKey("EXISTS", newKey) 113 | defer conn.Close(false) 114 | 115 | num, ok := conn.Do("EXISTS", newKey).(int) 116 | return ok && num == 1 117 | } 118 | 119 | func (c *Client) Incr(key string, delta int) int { 120 | newKey := c.BuildKey(key) 121 | conn := c.GetConnByKey("INCRBY", newKey) 122 | defer conn.Close(false) 123 | 124 | num, _ := conn.Do("INCRBY", newKey, delta).(int) 125 | return num 126 | } 127 | 128 | func (c *Client) set(key string, value interface{}, expire time.Duration, flag string) bool { 129 | newKey := c.BuildKey(key) 130 | conn := c.GetConnByKey("SET", newKey) 131 | defer conn.Close(false) 132 | 133 | var res interface{} 134 | if len(flag) == 0 { 135 | res = conn.Do("SET", newKey, value, "EX", expire/time.Second) 136 | } else { 137 | res = conn.Do("SET", newKey, value, "EX", expire/time.Second, flag) 138 | } 139 | 140 | payload, ok := res.([]byte) 141 | return ok && bytes.Equal(payload, replyOK) 142 | } 143 | 144 | func (c *Client) mset(items map[string]interface{}, expire time.Duration, flag string) bool { 145 | addrKeys, newKeys := c.AddrNewKeys("SET", items) 146 | wg, success := new(sync.WaitGroup), uint32(0) 147 | wg.Add(len(addrKeys)) 148 | for addr, keys := range addrKeys { 149 | go c.RunAddrFunc(addr, keys, wg, func(conn *Conn, keys []string) { 150 | for _, key := range keys { 151 | if oldKey := newKeys[key]; len(flag) == 0 { 152 | conn.WriteCmd("SET", key, items[oldKey], "EX", expire/time.Second) 153 | } else { 154 | conn.WriteCmd("SET", key, items[oldKey], "EX", expire/time.Second, flag) 155 | } 156 | } 157 | 158 | for range keys { 159 | payload, ok := conn.ReadReply().([]byte) 160 | if ok && bytes.Equal(payload, replyOK) { 161 | atomic.AddUint32(&success, 1) 162 | } 163 | } 164 | }) 165 | } 166 | 167 | wg.Wait() 168 | 169 | return success == uint32(len(items)) 170 | } 171 | 172 | // args = [0:"key"] 173 | func (c *Client) Do(cmd string, args ...interface{}) interface{} { 174 | if len(args) == 0 { 175 | panic("The length of args has to be greater than 1") 176 | } 177 | 178 | key, ok := args[0].(string) 179 | if ok == false { 180 | panic("Invalid key string:" + Util.ToString(args[0])) 181 | } 182 | 183 | cmd = strings.ToUpper(cmd) 184 | if Util.SliceSearchString(allRedisCmd, cmd) == -1 { 185 | panic("Undefined command:" + cmd) 186 | } 187 | 188 | newKey := c.BuildKey(key) 189 | conn := c.GetConnByKey(cmd, newKey) 190 | defer conn.Close(false) 191 | 192 | args[0] = newKey 193 | 194 | return conn.Do(cmd, args ...) 195 | } 196 | -------------------------------------------------------------------------------- /Client/Redis/MasterSlavePool.go: -------------------------------------------------------------------------------- 1 | package Redis 2 | 3 | import ( 4 | "bytes" 5 | "math/rand" 6 | "os" 7 | "strings" 8 | "time" 9 | 10 | "github.com/pinguo/pgo" 11 | "github.com/pinguo/pgo/Util" 12 | ) 13 | 14 | type MasterSlavePool struct { 15 | pool *Pool 16 | 17 | master string 18 | slaves []string 19 | } 20 | 21 | func newMasterSlavePool(pool *Pool) interface{} { 22 | return &MasterSlavePool{ 23 | pool: pool, 24 | } 25 | } 26 | 27 | // prevDft 上一个默认addr 一般在master-slave用于mset mget mdel 28 | func (m *MasterSlavePool) getAddrByKey(cmd, key, prevDft string) string { 29 | if prevDft != "" { 30 | return prevDft 31 | } 32 | m.pool.lock.RLock() 33 | defer m.pool.lock.RUnlock() 34 | if Util.SliceSearchString(allRedisReadCmd, cmd) >= 0 && len(m.slaves) > 0 { 35 | rand.Seed(time.Now().UnixNano()) 36 | index := rand.Intn(len(m.slaves)) 37 | return m.slaves[index] 38 | } else { 39 | return m.master 40 | } 41 | } 42 | 43 | // timing check master salve 44 | func (m *MasterSlavePool) check(addr, aType string) { 45 | oldMaster := m.master 46 | oldSlaves := m.slaves 47 | 48 | if addr == m.master { 49 | // is master 50 | newMaster := m.startCheckMaster() 51 | if newMaster == "" { 52 | // 未找到master 强制重找 53 | m.pool.reCheck = addr 54 | } else { 55 | m.pool.reCheck = "" 56 | } 57 | m.delSlave(m.master) 58 | m.delSlave(addr) 59 | 60 | } else { 61 | // is slave 62 | serverInfo := m.pool.servers[addr] 63 | 64 | for i := 0; i < serverInfo.weight; i++ { 65 | if aType == NodeActionAdd { 66 | m.slaves = append(m.slaves, addr) 67 | m.delSlave(m.master) 68 | } else if aType == NodeActionDel { 69 | m.delSlave(addr) 70 | } 71 | } 72 | 73 | } 74 | 75 | if len(m.slaves) == 0 { 76 | // check master 77 | m.startCheckMaster() 78 | 79 | // check slave 80 | m.startCheckSlaves() 81 | } 82 | 83 | if m.master == "" { 84 | pgo.GLogger().Error("Timing check,No master redis server in master-slave config!") 85 | return 86 | } 87 | 88 | if len(m.slaves) == 0 { 89 | pgo.GLogger().Error("Timing check,No slaves redis server in master-slave config!") 90 | return 91 | } 92 | 93 | if oldMaster != m.master { 94 | pgo.GLogger().Warn("Redis Proxy master node change, " + oldMaster + " to " + m.master) 95 | } 96 | 97 | diffSlaves := make([]string, 0) 98 | for _, v := range oldSlaves { 99 | if Util.SliceSearchString(m.slaves, v) == -1 { 100 | diffSlaves = append(diffSlaves, v) 101 | } 102 | } 103 | 104 | if len(diffSlaves) > 0 { 105 | pgo.GLogger().Warn("Redis Proxy slave nodes change , (" + strings.Join(oldSlaves, ",") + ") to (" + strings.Join(m.slaves, ",") + ")") 106 | } 107 | 108 | } 109 | 110 | func (m *MasterSlavePool) delSlave(addr string) { 111 | 112 | if index := Util.SliceSearchString(m.slaves, addr); index >= 0 { 113 | m.pool.lock.Lock() 114 | defer m.pool.lock.Unlock() 115 | m.slaves = append(m.slaves[0:index], m.slaves[index+1:]...) 116 | } 117 | } 118 | 119 | // first check master and check slave 120 | func (m *MasterSlavePool) startCheck() { 121 | // check master 122 | m.startCheckMaster() 123 | // check slave 124 | m.startCheckSlaves() 125 | 126 | if m.master == "" { 127 | panic("No master redis server in master-slave config!") 128 | } 129 | 130 | if len(m.slaves) == 0 { 131 | panic("No slave redis server in master-slave config!") 132 | } 133 | 134 | return 135 | } 136 | 137 | func (m *MasterSlavePool) startCheckMaster() string { 138 | master := "" 139 | for addr, serverInfo := range m.pool.servers { 140 | if serverInfo.disabled == true { 141 | continue 142 | } 143 | 144 | master = m.checkMaster(addr) 145 | if master != "" { 146 | break 147 | } 148 | } 149 | return master 150 | } 151 | 152 | func (m *MasterSlavePool) startCheckSlaves() { 153 | // check slaves 154 | if len(m.pool.servers) == 1 { 155 | m.slaves = append(m.slaves, m.master) 156 | } else { 157 | for addr, serverInfo := range m.pool.servers { 158 | if serverInfo.disabled == true { 159 | continue 160 | } 161 | 162 | if addr == m.master { 163 | continue 164 | } 165 | 166 | m.checkSlave(addr) 167 | } 168 | } 169 | 170 | // only master active 171 | if len(m.slaves) == 0 { 172 | m.checkSlave(m.master) 173 | } 174 | } 175 | 176 | // check master 177 | func (m *MasterSlavePool) checkMaster(addr string) string { 178 | defer func() { 179 | recover() 180 | }() 181 | 182 | conn := m.pool.GetConnByAddr(addr) 183 | defer conn.Close(false) 184 | res := conn.Do("SET", m.getCheckKey(), 1, "EX", 5*time.Second) 185 | payload, ok := res.([]byte) 186 | if ok && bytes.Equal(payload, replyOK) { 187 | func() { 188 | m.pool.lock.Lock() 189 | defer m.pool.lock.Unlock() 190 | m.master = addr 191 | }() 192 | 193 | return addr 194 | } 195 | 196 | return "" 197 | } 198 | 199 | // check slave 200 | func (m *MasterSlavePool) checkSlave(addr string) string { 201 | defer func() { 202 | recover() 203 | }() 204 | 205 | conn := m.pool.GetConnByAddr(addr) 206 | defer conn.Close(false) 207 | 208 | conn.Do("GET", m.getCheckKey()) 209 | serverInfo := m.pool.servers[addr] 210 | for i := 0; i < serverInfo.weight; i++ { 211 | m.slaves = append(m.slaves, addr) 212 | } 213 | 214 | return addr 215 | //ret := pgo.NewValue(retI) 216 | //if ret.Int() == 1 { 217 | // serverInfo := m.pool.servers[addr] 218 | // for i := 0; i < serverInfo.weight; i++ { 219 | // m.slaves = append(m.slaves, addr) 220 | // } 221 | // 222 | // return addr 223 | //} 224 | // 225 | //return "" 226 | } 227 | 228 | func (m *MasterSlavePool) getCheckKey() string { 229 | hostName, _ := os.Hostname() 230 | return PgoMasterSlaveCheckPrefix + hostName 231 | } 232 | -------------------------------------------------------------------------------- /Util/Misc.go: -------------------------------------------------------------------------------- 1 | package Util 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "hash/crc32" 7 | "math/rand" 8 | "net" 9 | "os" 10 | "regexp" 11 | "runtime" 12 | "strconv" 13 | "strings" 14 | "sync/atomic" 15 | "time" 16 | ) 17 | 18 | var ( 19 | seqId uint32 20 | ipAddr []byte 21 | 22 | // env expand regexp: ${env}, ${env||default} 23 | envRe = regexp.MustCompile(`\$\{[^\}\|]+(\|\|[^\$\{\}]+?)?\}`) 24 | 25 | // lang format regexp: zh-cn, zh, en_US 26 | langRe = regexp.MustCompile(`(?i)([a-z]+)(?:[_-]([a-z]+))?`) 27 | 28 | // stack trace regexp: /path/to/src/file.go:line 29 | traceRe = regexp.MustCompile(`^\t(.*)/src/(.*:\d+)\s`) 30 | 31 | // version format regexp: v10.1.0 32 | verFmtRe = regexp.MustCompile(`(?i)^v?(\d+\.*)+`) 33 | 34 | // version element regexp: [0-9]+ or [a-z]+ 35 | verEleRe = regexp.MustCompile(`(?i)\d+|[a-z]+`) 36 | ) 37 | 38 | func init() { 39 | // generate a random sequence id 40 | random := rand.New(rand.NewSource(time.Now().UnixNano())) 41 | seqId = random.Uint32() 42 | 43 | // get ipv4 address 44 | if addrs, e := net.InterfaceAddrs(); e == nil { 45 | for _, v := range addrs { 46 | if ip, ok := v.(*net.IPNet); ok { 47 | ipv4 := ip.IP.To4() 48 | if !ip.IP.IsLoopback() && ipv4 != nil { 49 | ipAddr = []byte(ipv4.String()) 50 | } 51 | } 52 | } 53 | } 54 | } 55 | 56 | // GenUniqueId generate a 24 bytes unique id 57 | func GenUniqueId() string { 58 | now := time.Now() 59 | seq := atomic.AddUint32(&seqId, 1) 60 | 61 | return fmt.Sprintf("%08x%06x%04x%06x", 62 | now.Unix()&0xFFFFFFFF, 63 | crc32.ChecksumIEEE(ipAddr)&0xFFFFFF, 64 | os.Getpid()&0xFFFF, 65 | seq&0xFFFFFF, 66 | ) 67 | } 68 | 69 | // ExpandEnv expand env variables, format: ${env}, ${env||default} 70 | func ExpandEnv(data []byte) []byte { 71 | rf := func(s []byte) []byte { 72 | tmp := bytes.Split(s[2:len(s)-1], []byte{'|', '|'}) 73 | env := bytes.TrimSpace(tmp[0]) 74 | 75 | if val, ok := os.LookupEnv(string(env)); ok { 76 | // return env value 77 | return []byte(val) 78 | } else if len(tmp) > 1 { 79 | // return default value 80 | return bytes.TrimSpace(tmp[1]) 81 | } 82 | 83 | // return original 84 | return s 85 | } 86 | 87 | return envRe.ReplaceAllFunc(data, rf) 88 | } 89 | 90 | // FormatLanguage format lang to ll-CC format 91 | func FormatLanguage(lang string) string { 92 | matches := langRe.FindStringSubmatch(lang) 93 | if len(matches) != 3 { 94 | return "" 95 | } 96 | 97 | matches[1] = strings.ToLower(matches[1]) 98 | matches[2] = strings.ToUpper(matches[2]) 99 | 100 | switch matches[2] { 101 | case "CHS", "HANS": 102 | matches[2] = "CN" 103 | case "CHT", "HANT": 104 | matches[2] = "TW" 105 | } 106 | 107 | if len(matches[2]) == 0 { 108 | return matches[1] 109 | } 110 | 111 | return matches[1] + "-" + matches[2] 112 | } 113 | 114 | // PanicTrace get panic trace 115 | func PanicTrace(maxDepth int, multiLine bool) string { 116 | buf := make([]byte, 1024) 117 | if n := runtime.Stack(buf, false); n < len(buf) { 118 | buf = buf[:n] 119 | } 120 | 121 | stack := bytes.NewBuffer(buf) 122 | sources := make([]string, 0, maxDepth) 123 | meetPanic := false 124 | 125 | for { 126 | line, err := stack.ReadString('\n') 127 | if err != nil || len(sources) >= maxDepth { 128 | break 129 | } 130 | 131 | mat := traceRe.FindStringSubmatch(line) 132 | if mat == nil { 133 | continue 134 | } 135 | 136 | // skip until first panic 137 | if strings.HasPrefix(mat[2], "runtime/panic.go") { 138 | meetPanic = true 139 | continue 140 | } 141 | 142 | // skip system file 143 | if strings.HasPrefix(mat[1], runtime.GOROOT()) { 144 | continue 145 | } 146 | 147 | if meetPanic { 148 | sources = append(sources, mat[2]) 149 | } 150 | } 151 | 152 | if multiLine { 153 | return strings.Join(sources, "\n") 154 | } 155 | 156 | return strings.Join(sources, ",") 157 | } 158 | 159 | // FormatVersion format version to have minimum depth, 160 | // eg. FormatVersion("v10...2....2.1-alpha", 5) == "v10.2.2.1.0-alpha" 161 | func FormatVersion(ver string, minDepth int) string { 162 | replaceFunc := func(s string) string { 163 | p, n := strings.Split(s, "."), 0 164 | for i, j := 0, len(p); i < j; i++ { 165 | if len(p[i]) > 0 { 166 | if n != i { 167 | p[n] = p[i] 168 | } 169 | n++ 170 | } 171 | } 172 | 173 | for p = p[:n]; n < minDepth; n++ { 174 | p = append(p, "0") 175 | } 176 | 177 | return strings.Join(p, ".") 178 | } 179 | 180 | return verFmtRe.ReplaceAllStringFunc(ver, replaceFunc) 181 | } 182 | 183 | // VersionCompare compare versions like version_compare of php, 184 | // special version strings these are handled in the following order, 185 | // (any string not found) < dev < alpha = a < beta = b < rc < #(empty) < ##(digit) < pl = p, 186 | // result: -1(ver1 < ver2), 0(ver1 == ver2), 1(ver1 > ver2) 187 | func VersionCompare(ver1, ver2 string) int { 188 | // trim leading v character 189 | ver1 = strings.TrimLeft(ver1, "vV") 190 | ver2 = strings.TrimLeft(ver2, "vV") 191 | 192 | v1 := verEleRe.FindAllStringSubmatch(ver1, -1) 193 | v2 := verEleRe.FindAllStringSubmatch(ver2, -1) 194 | 195 | vm := map[string]int{"dev": 1, "alpha": 2, "a": 2, "beta": 3, "b": 3, "rc": 4, "#": 5, "##": 6, "pl": 7, "p": 7} 196 | 197 | isDigit := func(b byte) bool { 198 | return '0' <= b && b <= '9' 199 | } 200 | 201 | compare := func(p1, p2 string) int { 202 | l1, l2 := 0, 0 203 | if isDigit(p1[0]) && isDigit(p2[0]) { 204 | l1, _ = strconv.Atoi(p1) 205 | l2, _ = strconv.Atoi(p2) 206 | } else if !isDigit(p1[0]) && !isDigit(p2[0]) { 207 | l1 = vm[strings.ToLower(p1)] 208 | l2 = vm[strings.ToLower(p2)] 209 | } else if isDigit(p1[0]) { 210 | l1 = vm["##"] 211 | l2 = vm[strings.ToLower(p2)] 212 | } else { 213 | l1 = vm[strings.ToLower(p1)] 214 | l2 = vm["##"] 215 | } 216 | 217 | if l1 > l2 { 218 | return 1 219 | } else if l1 < l2 { 220 | return -1 221 | } else { 222 | return 0 223 | } 224 | } 225 | 226 | c, n, l1, l2 := 0, 0, len(v1), len(v2) 227 | for ; n < l1 && n < l2 && c == 0; n++ { 228 | c = compare(v1[n][0], v2[n][0]) 229 | } 230 | 231 | if c == 0 { 232 | if n != l1 { 233 | c = compare(v1[n][0], "#") 234 | } else if n != l2 { 235 | c = compare("#", v2[n][0]) 236 | } 237 | } 238 | 239 | return c 240 | } 241 | -------------------------------------------------------------------------------- /Init.go: -------------------------------------------------------------------------------- 1 | package pgo 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "regexp" 7 | "strings" 8 | "time" 9 | 10 | "github.com/pinguo/pgo/Util" 11 | ) 12 | 13 | const ( 14 | ModeWeb = 1 15 | ModeCmd = 2 16 | DefaultEnv = "develop" 17 | DefaultController = "Index" 18 | DefaultAction = "Index" 19 | DefaultHttpAddr = "0.0.0.0:8000" 20 | DefaultTimeout = 30 * time.Second 21 | DefaultHeaderBytes = 1 << 20 22 | ControllerWeb = "Controller" 23 | ControllerCmd = "Command" 24 | ConstructMethod = "Construct" 25 | InitMethod = "Init" 26 | VendorPrefix = "vendor/" 27 | VendorLength = 7 28 | ActionPrefix = "Action" 29 | ActionLength = 6 30 | TraceMaxDepth = 10 31 | MaxPlugins = 32 32 | MaxCacheObjects = 100 33 | ) 34 | 35 | var ( 36 | App = &Application{} 37 | appTime = time.Now() 38 | aliasMap = make(map[string]string) 39 | aliasRe = regexp.MustCompile(`^@[^\\/]+`) 40 | logger *Logger 41 | EmptyObject struct{} 42 | ) 43 | 44 | // Map alias for map[string]interface{} 45 | type Map map[string]interface{} 46 | 47 | func init() { 48 | // initialize app 49 | ConstructAndInit(App, nil) 50 | 51 | // bind core object 52 | App.container.Bind(&Router{}) 53 | App.container.Bind(&Log{}) 54 | App.container.Bind(&ConsoleTarget{}) 55 | App.container.Bind(&FileTarget{}) 56 | App.container.Bind(&Status{}) 57 | App.container.Bind(&I18n{}) 58 | App.container.Bind(&View{}) 59 | App.container.Bind(&Gzip{}) 60 | App.container.Bind(&File{}) 61 | } 62 | 63 | // Run run app 64 | func Run() { 65 | App.GetServer().Serve() 66 | } 67 | 68 | // GLogger get global logger 69 | func GLogger() *Logger { 70 | if logger == nil { 71 | // defer creation to first call, give opportunity to customize log target 72 | logger = App.GetLog().GetLogger(App.name, Util.GenUniqueId()) 73 | } 74 | 75 | return logger 76 | } 77 | 78 | // TimeRun time duration since app run 79 | func TimeRun() time.Duration { 80 | d := time.Since(appTime) 81 | d -= d % time.Second 82 | return d 83 | } 84 | 85 | // SetAlias set path alias, eg. @app => /path/to/base 86 | func SetAlias(alias, path string) { 87 | if len(alias) > 0 && alias[0] != '@' { 88 | alias = "@" + alias 89 | } 90 | 91 | if strings.IndexAny(alias, `\/`) != -1 { 92 | panic("SetAlias: invalid alias, " + alias) 93 | } 94 | 95 | if len(alias) <= 1 || len(path) == 0 { 96 | panic("SetAlias: empty alias or path, " + alias) 97 | } 98 | 99 | aliasMap[alias] = path 100 | } 101 | 102 | // GetAlias resolve path alias, eg. @runtime/app.log => /path/to/runtime/app.log 103 | func GetAlias(alias string) string { 104 | if prefix := aliasRe.FindString(alias); len(prefix) == 0 { 105 | return alias // not an alias 106 | } else if path, ok := aliasMap[prefix]; ok { 107 | return strings.Replace(alias, prefix, path, 1) 108 | } 109 | 110 | return "" 111 | } 112 | 113 | // CreateObject create object using the given configuration, 114 | // class can be a string or a map contain "class" field, 115 | // if a map is specified, fields except "class" will be 116 | // treated as properties of the object to be created, 117 | // params is optional parameters for Construct method. 118 | func CreateObject(class interface{}, params ...interface{}) interface{} { 119 | var className string 120 | var config map[string]interface{} 121 | 122 | switch v := class.(type) { 123 | case string: 124 | className = v 125 | case map[string]interface{}: 126 | if _, ok := v["class"]; !ok { 127 | panic(`CreateObject: class configuration require "class" field`) 128 | } 129 | 130 | className = v["class"].(string) 131 | config = v 132 | default: 133 | panic(fmt.Sprintf("CreateObject: unsupported class type: %T", class)) 134 | } 135 | 136 | if name := GetAlias(className); len(name) > 0 { 137 | return App.GetContainer().Get(name, config, params...).Interface() 138 | } 139 | 140 | panic("unknown class: " + className) 141 | } 142 | 143 | // Configure configure object using the given configuration, 144 | // obj is a pointer or reflect.Value of a pointer, 145 | // config is the configuration map for properties. 146 | func Configure(obj interface{}, config map[string]interface{}) { 147 | // skip empty configuration 148 | if n := len(config); n == 0 { 149 | return 150 | } else if n == 1 { 151 | if _, ok := config["class"]; ok { 152 | return 153 | } 154 | } 155 | 156 | // v refer to the object pointer 157 | var v reflect.Value 158 | if _, ok := obj.(reflect.Value); ok { 159 | v = obj.(reflect.Value) 160 | } else { 161 | v = reflect.ValueOf(obj) 162 | } 163 | 164 | if v.Kind() != reflect.Ptr { 165 | panic("Configure: obj require a pointer or reflect.Value of a pointer") 166 | } 167 | 168 | // rv refer to the value of pointer 169 | rv := v.Elem() 170 | 171 | for key, val := range config { 172 | if key == "class" { 173 | continue 174 | } 175 | 176 | // change key to title string 177 | key = strings.Title(key) 178 | 179 | // check object's setter method 180 | if method := v.MethodByName("Set" + key); method.IsValid() { 181 | newVal := reflect.ValueOf(val).Convert(method.Type().In(0)) 182 | method.Call([]reflect.Value{newVal}) 183 | continue 184 | } 185 | 186 | // check object's public field 187 | field := rv.FieldByName(key) 188 | if field.IsValid() && field.CanSet() { 189 | newVal := reflect.ValueOf(val).Convert(field.Type()) 190 | field.Set(newVal) 191 | continue 192 | } 193 | } 194 | } 195 | 196 | // ConstructAndInit construct and initialize object, 197 | // obj is a pointer or reflect.Value of a pointer, 198 | // config is configuration map for properties, 199 | // params is optional parameters for Construct method. 200 | func ConstructAndInit(obj interface{}, config map[string]interface{}, params ...interface{}) { 201 | var v reflect.Value 202 | if _, ok := obj.(reflect.Value); ok { 203 | v = obj.(reflect.Value) 204 | } else { 205 | v = reflect.ValueOf(obj) 206 | } 207 | 208 | if v.Kind() != reflect.Ptr { 209 | panic("ConstructAndInit: obj require a pointer or reflect.Value of a pointer") 210 | } 211 | 212 | // call Construct method 213 | if cm := v.MethodByName(ConstructMethod); cm.IsValid() { 214 | in := make([]reflect.Value, 0) 215 | for _, arg := range params { 216 | in = append(in, reflect.ValueOf(arg)) 217 | } 218 | 219 | cm.Call(in) 220 | } 221 | 222 | // configure the object 223 | Configure(v, config) 224 | 225 | // call Init method 226 | if im := v.MethodByName(InitMethod); im.IsValid() { 227 | in := make([]reflect.Value, 0) 228 | im.Call(in) 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /Client/Memcache/Pool.go: -------------------------------------------------------------------------------- 1 | package Memcache 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strings" 7 | "sync" 8 | "time" 9 | 10 | "github.com/pinguo/pgo/Util" 11 | ) 12 | 13 | type serverInfo struct { 14 | weight int 15 | disabled bool 16 | } 17 | 18 | type connList struct { 19 | count int 20 | head *Conn 21 | tail *Conn 22 | } 23 | 24 | type Pool struct { 25 | lock sync.RWMutex 26 | hashRing *Util.HashRing 27 | connLists map[string]*connList 28 | servers map[string]*serverInfo 29 | 30 | prefix string 31 | maxIdleConn int 32 | maxIdleTime time.Duration 33 | netTimeout time.Duration 34 | probeInterval time.Duration 35 | } 36 | 37 | func (p *Pool) Construct() { 38 | p.hashRing = Util.NewHashRing() 39 | p.connLists = make(map[string]*connList) 40 | p.servers = make(map[string]*serverInfo) 41 | 42 | p.prefix = defaultPrefix 43 | p.maxIdleConn = defaultIdleConn 44 | p.maxIdleTime = defaultIdleTime 45 | p.netTimeout = defaultTimeout 46 | p.probeInterval = defaultProbe 47 | } 48 | 49 | func (p *Pool) Init() { 50 | if len(p.servers) == 0 { 51 | p.servers[defaultServer] = &serverInfo{weight: 1} 52 | } 53 | 54 | for addr, item := range p.servers { 55 | p.hashRing.AddNode(addr, item.weight) 56 | } 57 | 58 | if p.probeInterval != 0 { 59 | if p.probeInterval > maxProbeInterval { 60 | p.probeInterval = maxProbeInterval 61 | } else if p.probeInterval < minProbeInterval { 62 | p.probeInterval = minProbeInterval 63 | } 64 | 65 | go p.probeLoop() 66 | } 67 | } 68 | 69 | func (p *Pool) SetPrefix(prefix string) { 70 | p.prefix = prefix 71 | } 72 | 73 | func (p *Pool) SetServers(v []interface{}) { 74 | for _, vv := range v { 75 | addr := vv.(string) 76 | if pos := strings.Index(addr, "://"); pos != -1 { 77 | addr = addr[pos+3:] 78 | } 79 | 80 | item := p.servers[addr] 81 | if item == nil { 82 | item = &serverInfo{} 83 | p.servers[addr] = item 84 | } 85 | 86 | item.weight += 1 87 | } 88 | } 89 | 90 | func (p *Pool) GetServers() (servers []string) { 91 | for server := range p.servers { 92 | servers = append(servers, server) 93 | } 94 | return 95 | } 96 | 97 | func (p *Pool) SetMaxIdleConn(v int) { 98 | p.maxIdleConn = v 99 | } 100 | 101 | func (p *Pool) SetMaxIdleTime(v string) { 102 | if maxIdleTime, e := time.ParseDuration(v); e != nil { 103 | panic(fmt.Sprintf(errSetProp, "maxIdleTime", e)) 104 | } else { 105 | p.maxIdleTime = maxIdleTime 106 | } 107 | } 108 | 109 | func (p *Pool) SetNetTimeout(v string) { 110 | if netTimeout, e := time.ParseDuration(v); e != nil { 111 | panic(fmt.Sprintf(errSetProp, "netTimeout", e)) 112 | } else { 113 | p.netTimeout = netTimeout 114 | } 115 | } 116 | 117 | func (p *Pool) SetProbeInterval(v string) { 118 | if probeInterval, e := time.ParseDuration(v); e != nil { 119 | panic(fmt.Sprintf(errSetProp, "probeInterval", e)) 120 | } else { 121 | p.probeInterval = probeInterval 122 | } 123 | } 124 | 125 | func (p *Pool) BuildKey(key string) string { 126 | return p.prefix + key 127 | } 128 | 129 | func (p *Pool) AddrNewKeys(v interface{}) (map[string][]string, map[string]string) { 130 | addrKeys, newKeys := make(map[string][]string), make(map[string]string) 131 | switch vv := v.(type) { 132 | case []string: 133 | for _, key := range vv { 134 | newKey := p.BuildKey(key) 135 | addr := p.GetAddrByKey(newKey) 136 | newKeys[newKey] = key 137 | addrKeys[addr] = append(addrKeys[addr], newKey) 138 | } 139 | case map[string]interface{}: 140 | for key := range vv { 141 | newKey := p.BuildKey(key) 142 | addr := p.GetAddrByKey(newKey) 143 | newKeys[newKey] = key 144 | addrKeys[addr] = append(addrKeys[addr], newKey) 145 | } 146 | default: 147 | panic(errBase + "addr new keys invalid") 148 | } 149 | return addrKeys, newKeys 150 | } 151 | 152 | func (p *Pool) RunAddrFunc(addr string, keys []string, wg *sync.WaitGroup, f func(*Conn, []string)) { 153 | defer func() { 154 | recover() // ignore panic 155 | wg.Done() // notify done 156 | }() 157 | 158 | conn := p.GetConnByAddr(addr) 159 | defer conn.Close(false) 160 | 161 | f(conn, keys) 162 | } 163 | 164 | func (p *Pool) GetConnByKey(key string) *Conn { 165 | if addr := p.GetAddrByKey(key); len(addr) == 0 { 166 | panic(errNoServer) 167 | } else { 168 | return p.GetConnByAddr(addr) 169 | } 170 | } 171 | 172 | func (p *Pool) GetConnByAddr(addr string) *Conn { 173 | conn := p.getFreeConn(addr) 174 | if conn == nil || !p.checkConn(conn) { 175 | conn = p.dial(addr) 176 | } 177 | 178 | conn.ExtendDeadLine() 179 | return conn 180 | } 181 | 182 | func (p *Pool) GetAddrByKey(key string) string { 183 | p.lock.RLock() 184 | defer p.lock.RUnlock() 185 | return p.hashRing.GetNode(key) 186 | } 187 | 188 | func (p *Pool) getFreeConn(addr string) *Conn { 189 | p.lock.Lock() 190 | defer p.lock.Unlock() 191 | 192 | list := p.connLists[addr] 193 | if list == nil || list.count == 0 { 194 | return nil 195 | } 196 | 197 | conn := list.head 198 | if list.count--; list.count == 0 { 199 | list.head, list.tail = nil, nil 200 | } else { 201 | list.head, conn.next.prev = conn.next, nil 202 | } 203 | 204 | conn.next = nil 205 | return conn 206 | } 207 | 208 | func (p *Pool) putFreeConn(conn *Conn) bool { 209 | p.lock.Lock() 210 | defer p.lock.Unlock() 211 | 212 | list := p.connLists[conn.addr] 213 | if list == nil { 214 | list = new(connList) 215 | p.connLists[conn.addr] = list 216 | } 217 | 218 | if list.count >= p.maxIdleConn { 219 | return false 220 | } 221 | 222 | if list.count == 0 { 223 | list.head, list.tail = conn, conn 224 | conn.prev, conn.next = nil, nil 225 | } else { 226 | conn.prev, conn.next = list.tail, nil 227 | conn.prev.next, list.tail = conn, conn 228 | } 229 | 230 | list.count++ 231 | return true 232 | } 233 | 234 | func (p *Pool) checkConn(conn *Conn) bool { 235 | defer func() { 236 | // if panic, return value is default(false) 237 | if v := recover(); v != nil { 238 | conn.Close(true) 239 | } 240 | }() 241 | 242 | if !conn.CheckActive() { 243 | conn.Close(true) 244 | return false 245 | } 246 | return true 247 | } 248 | 249 | func (p *Pool) dial(addr string) *Conn { 250 | if nc, e := net.DialTimeout(p.parseNetwork(addr), addr, p.netTimeout); e != nil { 251 | panic(errBase + e.Error()) 252 | } else { 253 | return newConn(addr, nc, p) 254 | } 255 | } 256 | 257 | func (p *Pool) parseNetwork(addr string) string { 258 | if pos := strings.IndexByte(addr, '/'); pos != -1 { 259 | return "unix" 260 | } else { 261 | return "tcp" 262 | } 263 | } 264 | 265 | func (p *Pool) probeServer(addr string) { 266 | nc, e := net.DialTimeout(p.parseNetwork(addr), addr, p.netTimeout) 267 | if e != nil && !p.servers[addr].disabled { 268 | p.lock.Lock() 269 | p.servers[addr].disabled = true 270 | p.hashRing.DelNode(addr) 271 | p.lock.Unlock() 272 | } else if e == nil && p.servers[addr].disabled { 273 | p.lock.Lock() 274 | p.servers[addr].disabled = false 275 | p.hashRing.AddNode(addr, p.servers[addr].weight) 276 | p.lock.Unlock() 277 | } 278 | 279 | if e == nil { 280 | nc.Close() 281 | } 282 | } 283 | 284 | func (p *Pool) probeLoop() { 285 | for { 286 | <-time.After(p.probeInterval) 287 | for addr := range p.servers { 288 | p.probeServer(addr) 289 | } 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /Config.go: -------------------------------------------------------------------------------- 1 | package pgo 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | "sync" 11 | 12 | "github.com/pinguo/pgo/Util" 13 | ) 14 | 15 | // Config the config component 16 | type Config struct { 17 | parsers map[string]IConfigParser 18 | data map[string]interface{} 19 | paths []string 20 | lock sync.RWMutex 21 | } 22 | 23 | func (c *Config) Construct() { 24 | c.parsers = make(map[string]IConfigParser) 25 | c.data = make(map[string]interface{}) 26 | c.paths = make([]string, 0) 27 | 28 | confPath := filepath.Join(App.GetBasePath(), "conf") 29 | if f, _ := os.Stat(confPath); f != nil && f.IsDir() { 30 | c.paths = append(c.paths, confPath) 31 | } else { 32 | panic("Config: invalid conf path, " + confPath) 33 | } 34 | 35 | envPath := filepath.Join(confPath, App.GetEnv()) 36 | if f, _ := os.Stat(envPath); f != nil && f.IsDir() { 37 | c.paths = append(c.paths, envPath) 38 | } else if App.GetEnv() != DefaultEnv { 39 | panic("Config: invalid env path, " + envPath) 40 | } 41 | 42 | c.AddParser("json", &JsonConfigParser{}) 43 | c.AddParser("yaml", &YamlConfigParser{}) 44 | } 45 | 46 | // AddParser add parser for file with ext extension 47 | func (c *Config) AddParser(ext string, parser IConfigParser) { 48 | c.parsers[ext] = parser 49 | } 50 | 51 | // AddPath add path to end of search paths 52 | func (c *Config) AddPath(path string) { 53 | paths := make([]string, 0) 54 | for _, v := range c.paths { 55 | if v != path { 56 | paths = append(paths, v) 57 | } 58 | } 59 | 60 | c.paths = append(paths, path) 61 | } 62 | 63 | // GetBool get bool value from config, 64 | // key is dot separated config key, 65 | // dft is default value if key not exists. 66 | func (c *Config) GetBool(key string, dft bool) bool { 67 | if v := c.Get(key); v != nil { 68 | return Util.ToBool(v) 69 | } 70 | 71 | return dft 72 | } 73 | 74 | // GetInt get int value from config, 75 | // key is dot separated config key, 76 | // dft is default value if key not exists. 77 | func (c *Config) GetInt(key string, dft int) int { 78 | if v := c.Get(key); v != nil { 79 | return Util.ToInt(v) 80 | } 81 | 82 | return dft 83 | } 84 | 85 | // GetFloat get float value from config, 86 | // key is dot separated config key, 87 | // dft is default value if key not exists. 88 | func (c *Config) GetFloat(key string, dft float64) float64 { 89 | if v := c.Get(key); v != nil { 90 | return Util.ToFloat(v) 91 | } 92 | 93 | return dft 94 | } 95 | 96 | // GetString get string value from config, 97 | // key is dot separated config key, 98 | // dft is default value if key not exists. 99 | func (c *Config) GetString(key string, dft string) string { 100 | if v := c.Get(key); v != nil { 101 | return Util.ToString(v) 102 | } 103 | 104 | return dft 105 | } 106 | 107 | // GetSliceBool get []bool value from config, 108 | // key is dot separated config key, 109 | // nil is default value if key not exists. 110 | func (c *Config) GetSliceBool(key string) []bool { 111 | var ret []bool 112 | if v := c.Get(key); v != nil { 113 | if vI, ok := v.([]interface{}); ok == true { 114 | for _, vv := range vI { 115 | ret = append(ret, Util.ToBool(vv)) 116 | } 117 | } 118 | } 119 | 120 | return ret 121 | } 122 | 123 | // GetSliceInt get []int value from config, 124 | // key is dot separated config key, 125 | // nil is default value if key not exists. 126 | func (c *Config) GetSliceInt(key string) []int { 127 | var ret []int 128 | if v := c.Get(key); v != nil { 129 | if vI, ok := v.([]interface{}); ok == true { 130 | for _, vv := range vI { 131 | ret = append(ret, Util.ToInt(vv)) 132 | } 133 | } 134 | } 135 | 136 | return ret 137 | } 138 | 139 | // GetSliceFloat get []float value from config, 140 | // key is dot separated config key, 141 | // nil is default value if key not exists. 142 | func (c *Config) GetSliceFloat(key string) []float64 { 143 | var ret []float64 144 | if v := c.Get(key); v != nil { 145 | if vI, ok := v.([]interface{}); ok == true { 146 | for _, vv := range vI { 147 | ret = append(ret, Util.ToFloat(vv)) 148 | } 149 | } 150 | } 151 | 152 | return ret 153 | } 154 | 155 | // GetSliceString get []string value from config, 156 | // key is dot separated config key, 157 | // nil is default value if key not exists. 158 | func (c *Config) GetSliceString(key string) []string { 159 | var ret []string 160 | if v := c.Get(key); v != nil { 161 | if vI, ok := v.([]interface{}); ok == true { 162 | for _, vv := range vI { 163 | ret = append(ret, Util.ToString(vv)) 164 | } 165 | } 166 | } 167 | 168 | return ret 169 | } 170 | 171 | // Get get value by dot separated key, 172 | // the first part of key is file name 173 | // without extension. if key is empty, 174 | // all loaded config will be returned. 175 | func (c *Config) Get(key string) interface{} { 176 | ks := strings.Split(key, ".") 177 | if _, ok := c.data[ks[0]]; !ok { 178 | c.Load(ks[0]) 179 | } 180 | 181 | c.lock.RLock() 182 | defer c.lock.RUnlock() 183 | 184 | return Util.MapGet(c.data, key) 185 | } 186 | 187 | // Set set value by dot separated key, 188 | // if key is empty, the value will set 189 | // to root, if val is nil, the key will 190 | // be deleted. 191 | func (c *Config) Set(key string, val interface{}) { 192 | c.lock.Lock() 193 | defer c.lock.Unlock() 194 | 195 | Util.MapSet(c.data, key, val) 196 | } 197 | 198 | // Load load config file under the search paths. 199 | // file under env sub path will be merged. 200 | func (c *Config) Load(name string) { 201 | c.lock.Lock() 202 | defer c.lock.Unlock() 203 | 204 | // avoid repeated loading 205 | _, ok := c.data[name] 206 | if ok || len(name) == 0 { 207 | return 208 | } 209 | 210 | for _, path := range c.paths { 211 | files, _ := filepath.Glob(filepath.Join(path, name+".*")) 212 | for _, f := range files { 213 | ext := strings.ToLower(filepath.Ext(f)) 214 | if parser, ok := c.parsers[ext[1:]]; ok { 215 | if conf := parser.Parse(f); conf != nil { 216 | Util.MapMerge(c.data, map[string]interface{}{name: conf}) 217 | } 218 | } 219 | } 220 | } 221 | } 222 | 223 | // JsonConfigParser parser for json config 224 | type JsonConfigParser struct { 225 | } 226 | 227 | // Parse parse json config, environment value like ${env||default} will expand 228 | func (j *JsonConfigParser) Parse(path string) map[string]interface{} { 229 | h, e := os.Open(path) 230 | if e != nil { 231 | panic("JsonConfigParser: failed to open file: " + path) 232 | } 233 | 234 | defer h.Close() 235 | 236 | content, e := ioutil.ReadAll(h) 237 | if e != nil { 238 | panic("JsonConfigParser: failed to read file: " + path) 239 | } 240 | 241 | // expand env: ${env||default} 242 | content = Util.ExpandEnv(content) 243 | 244 | var data map[string]interface{} 245 | if e := json.Unmarshal(content, &data); e != nil { 246 | panic(fmt.Sprintf("jsonConfigParser: failed to parse file: %s, %s", path, e.Error())) 247 | } 248 | 249 | return data 250 | } 251 | 252 | // YamlConfigParser parser for yaml config 253 | type YamlConfigParser struct { 254 | } 255 | 256 | // Parse parse yaml config, environment value like ${env||default} will expand 257 | func (y *YamlConfigParser) Parse(path string) map[string]interface{} { 258 | h, e := os.Open(path) 259 | if e != nil { 260 | panic("YamlConfigParser: failed to open file: " + path) 261 | } 262 | 263 | defer h.Close() 264 | 265 | content, e := ioutil.ReadAll(h) 266 | if e != nil { 267 | panic("YamlConfigParser: failed to read file: " + path) 268 | } 269 | 270 | // expand env: ${env||default} 271 | content = Util.ExpandEnv(content) 272 | 273 | var data map[string]interface{} 274 | if e := Util.YamlUnmarshal(content, &data); e != nil { 275 | panic(fmt.Sprintf("YamlConfigParser: failed to parse file: %s, %s", path, e.Error())) 276 | } 277 | 278 | return data 279 | } 280 | -------------------------------------------------------------------------------- /Client/RabbitMq/Pool.go: -------------------------------------------------------------------------------- 1 | package RabbitMq 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strconv" 7 | "strings" 8 | "sync" 9 | "time" 10 | 11 | "github.com/pinguo/pgo" 12 | "github.com/pinguo/pgo/Util" 13 | ) 14 | 15 | type serverInfo struct { 16 | weight int64 17 | } 18 | 19 | type Pool struct { 20 | ServiceName string 21 | servers map[string]*serverInfo 22 | tlsRootCAs string 23 | tlsCert string 24 | tlsCertKey string 25 | user string 26 | pass string 27 | 28 | exchangeName string 29 | exchangeType string 30 | 31 | maxChannelNum int 32 | maxIdleChannel int 33 | maxIdleChannelTime time.Duration 34 | maxWaitTime time.Duration 35 | 36 | probeInterval time.Duration 37 | 38 | connList map[string]*ConnBox 39 | 40 | lock sync.RWMutex 41 | } 42 | 43 | func (c *Pool) Construct() { 44 | c.connList = make(map[string]*ConnBox) 45 | c.servers = make(map[string]*serverInfo) 46 | c.maxChannelNum = dftMaxChannelNum 47 | c.maxIdleChannel = dftMaxIdleChannel 48 | c.maxIdleChannelTime = dftMaxIdleChannelTime 49 | c.exchangeType = dftExchangeType 50 | c.exchangeName = dftExchangeName 51 | c.maxWaitTime = dftMaxWaitTime 52 | c.probeInterval = dftProbeInterval 53 | 54 | } 55 | 56 | func (c *Pool) Init() { 57 | if c.exchangeName == "" { 58 | panic("exchangeName cannot be empty") 59 | } 60 | 61 | if c.ServiceName == "" { 62 | panic("ServiceName cannot be empty") 63 | } 64 | 65 | if c.maxIdleChannel > c.maxChannelNum { 66 | panic("maxIdleChannel cannot be larger than maxChannelNum") 67 | } 68 | 69 | if c.probeInterval > 0 { 70 | go c.probeLoop() 71 | } 72 | 73 | } 74 | 75 | func (c *Pool) SetServers(v []interface{}) { 76 | for _, vv := range v { 77 | addr := vv.(string) 78 | 79 | if pos := strings.Index(addr, "://"); pos != -1 { 80 | addr = addr[pos+3:] 81 | } 82 | 83 | info := c.servers[addr] 84 | if info == nil { 85 | info = &serverInfo{} 86 | c.servers[addr] = info 87 | } 88 | 89 | info.weight += 1 90 | } 91 | } 92 | 93 | func (c *Pool) GetServers() (servers []string) { 94 | for server := range c.servers { 95 | servers = append(servers, server) 96 | } 97 | return servers 98 | } 99 | 100 | func (c *Pool) SetUser(v string) { 101 | c.user = v 102 | } 103 | 104 | func (c *Pool) SetPass(v string) { 105 | c.pass = v 106 | } 107 | 108 | func (c *Pool) SetTlsRootCAs(v string) { 109 | c.tlsRootCAs = v 110 | } 111 | 112 | func (c *Pool) SetTlsCert(v string) { 113 | c.tlsCert = v 114 | } 115 | 116 | func (c *Pool) SetTlsCertKey(v string) { 117 | c.tlsCertKey = v 118 | } 119 | 120 | func (c *Pool) SetExchangeName(v string) { 121 | c.exchangeName = v 122 | } 123 | 124 | func (c *Pool) SetServiceName(v string) { 125 | c.ServiceName = v 126 | } 127 | 128 | func (c *Pool) SetExchangeType(v string) { 129 | c.exchangeType = v 130 | } 131 | 132 | func (c *Pool) SetMaxChannelNum(v int) { 133 | c.maxChannelNum = v 134 | } 135 | 136 | func (c *Pool) SetMaxIdleChannel(v int) { 137 | c.maxIdleChannel = v 138 | } 139 | 140 | func (c *Pool) setMaxIdleChannelTime(v string) { 141 | if netTimeout, e := time.ParseDuration(v); e != nil { 142 | panic(fmt.Sprintf(errSetProp, "maxIdleChannelTime", e)) 143 | } else { 144 | c.maxIdleChannelTime = netTimeout 145 | } 146 | } 147 | 148 | func (c *Pool) setMaxWaitTime(v string) { 149 | if netTimeout, e := time.ParseDuration(v); e != nil || netTimeout <= 0 { 150 | panic(fmt.Sprintf(errSetProp, "maxWaitTime", e)) 151 | } else { 152 | c.maxWaitTime = netTimeout 153 | } 154 | } 155 | 156 | func (c *Pool) SetProbeInterval(v string) { 157 | if probeInterval, e := time.ParseDuration(v); e != nil { 158 | panic(fmt.Sprintf(errSetProp, "probeInterval", e)) 159 | } else { 160 | c.probeInterval = probeInterval 161 | } 162 | } 163 | 164 | func (c *Pool) getExchangeName() string { 165 | return "pgo." + c.exchangeName 166 | } 167 | 168 | func (c *Pool) getRouteKey(opCode string) string { 169 | return "pgo." + c.exchangeName + "." + opCode 170 | } 171 | 172 | // 获取channel链接 173 | func (c *Pool) getFreeChannel() *ChannelBox { 174 | connBox := c.getConnBox() 175 | connBox.useChannelCount++ 176 | 177 | var channelBox *ChannelBox 178 | 179 | select { 180 | case channelBox = <-connBox.channelList: 181 | default: 182 | } 183 | 184 | if channelBox == nil { 185 | return c.getChannelBox(connBox) 186 | } 187 | 188 | if time.Since(channelBox.lastActive) >= c.maxIdleChannelTime || channelBox.connStartTime != connBox.startTime { 189 | channelBox.Close(true) 190 | return c.getChannelBox(connBox) 191 | } 192 | return channelBox 193 | } 194 | 195 | // 获取ChannelBox 196 | func (c *Pool) getChannelBox(connBox *ConnBox) *ChannelBox { 197 | if connBox.useConnCount >= c.maxChannelNum { 198 | // 等待回收 199 | var channelBox *ChannelBox 200 | timeAfter := time.After(c.maxWaitTime) 201 | select { 202 | case channelBox = <-connBox.channelList: 203 | case <-timeAfter: 204 | } 205 | 206 | if channelBox == nil { 207 | panic("RabbitMq gets the channel timeout") 208 | } 209 | 210 | return channelBox 211 | } else { 212 | return newChannelBox(connBox, c) 213 | } 214 | } 215 | 216 | // 释放或者返回channel链接池 217 | func (c *Pool) putFreeChannel(channelBox *ChannelBox) bool { 218 | connBox := c.getConnBox(channelBox.connBoxId) 219 | 220 | if len(connBox.channelList) >= c.maxIdleChannel { 221 | connBox.useChannelCount-- 222 | return false 223 | } 224 | 225 | select { 226 | case connBox.channelList <- channelBox: 227 | default: 228 | } 229 | 230 | return true 231 | 232 | } 233 | 234 | // 获取tcp链接 235 | func (c *Pool) getConnBox(idDft ...string) *ConnBox { 236 | if len(c.connList) == 0 { 237 | c.initConn() 238 | } 239 | 240 | c.lock.RLock() 241 | defer c.lock.RUnlock() 242 | if len(idDft) > 0 { 243 | return c.connList[idDft[0]] 244 | } 245 | 246 | k := "" 247 | num := 0 248 | for i, connBox := range c.connList { 249 | if connBox.isClosed() { 250 | continue 251 | } 252 | cLen := len(connBox.channelList) 253 | if num == 0 || cLen > num { 254 | k = i 255 | num = cLen 256 | } 257 | } 258 | if k == "" { 259 | panic("Rabbit not found conn") 260 | } 261 | return c.connList[k] 262 | } 263 | 264 | // 设置tcp链接 265 | func (c *Pool) initConn() bool { 266 | 267 | c.lock.Lock() 268 | defer c.lock.Unlock() 269 | for addr, info := range c.servers { 270 | var i int64 271 | for i = 1; i <= info.weight; i++ { 272 | id := c.getConnId(addr, i) 273 | c.connList[id] = newConnBox(id, addr, c.getDsn(addr), c.maxChannelNum, c.tlsCert, c.tlsCertKey, c.tlsRootCAs) 274 | } 275 | 276 | } 277 | 278 | return true 279 | } 280 | 281 | func (c *Pool) getConnId(addr string, i int64) string { 282 | return addr + "_" + strconv.FormatInt(i, 10) 283 | } 284 | 285 | func (c *Pool) getDsn(addr string) string { 286 | return fmt.Sprintf("%s://%s:%s@%s", dftProtocol, c.user, c.pass, addr) 287 | } 288 | 289 | func (c *Pool) probeServer(addr string, weight int64) { 290 | nc, e := net.DialTimeout("tcp", addr, defaultTimeout) 291 | if e == nil { 292 | defer nc.Close() 293 | } 294 | 295 | var i int64 296 | for i = 1; i <= weight; i++ { 297 | id := c.getConnId(addr, i) 298 | connBox := c.getConnBox(id) 299 | func() { 300 | defer func() { 301 | if err := recover(); err != nil { 302 | pgo.GLogger().Error("Rabbit probeServer err:" + Util.ToString(err)) 303 | } 304 | }() 305 | 306 | if e != nil && !connBox.isClosed() { 307 | connBox.setDisable() 308 | } else if e == nil && connBox.isClosed() { 309 | connBox.setEnable() 310 | connBox.initConn() 311 | } 312 | }() 313 | 314 | } 315 | } 316 | 317 | func (c *Pool) probeLoop() { 318 | for { 319 | <-time.After(c.probeInterval) 320 | for addr, info := range c.servers { 321 | c.probeServer(addr, info.weight) 322 | } 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /Client/Db/Adapter.go: -------------------------------------------------------------------------------- 1 | package Db 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "strings" 7 | "time" 8 | 9 | "github.com/pinguo/pgo" 10 | "github.com/pinguo/pgo/Util" 11 | ) 12 | 13 | // Adapter of Db Client, add context support. 14 | // usage: db := this.GetObject(Db.AdapterClass).(*Db.Adapter) 15 | type Adapter struct { 16 | pgo.Object 17 | client *Client 18 | db *sql.DB 19 | tx *sql.Tx 20 | } 21 | 22 | func (a *Adapter) Construct(componentId ...string) { 23 | id := defaultComponentId 24 | if len(componentId) > 0 { 25 | id = componentId[0] 26 | } 27 | 28 | a.client = pgo.App.Get(id).(*Client) 29 | } 30 | 31 | func (a *Adapter) GetClient() *Client { 32 | return a.client 33 | } 34 | 35 | func (a *Adapter) GetDb(master bool) *sql.DB { 36 | // reuse previous db instance for read 37 | if !master && a.db != nil { 38 | return a.db 39 | } 40 | 41 | a.db = a.client.GetDb(master) 42 | return a.db 43 | } 44 | 45 | // Begin start a transaction with default timeout context and optional opts, 46 | // if opts is nil, default driver option will be used. 47 | func (a *Adapter) Begin(opts ...*sql.TxOptions) bool { 48 | opts = append(opts, nil) 49 | ctx, _ := context.WithTimeout(context.Background(), defaultTimeout) 50 | return a.BeginContext(ctx, opts[0]) 51 | } 52 | 53 | // BeginContext start a transaction with specified context and optional opts, 54 | // if opts is nil, default driver option will be used. 55 | func (a *Adapter) BeginContext(ctx context.Context, opts *sql.TxOptions) bool { 56 | if tx, e := a.GetDb(true).BeginTx(ctx, opts); e != nil { 57 | a.GetContext().Error("Db.Begin error, " + e.Error()) 58 | return false 59 | } else { 60 | a.tx = tx 61 | return true 62 | } 63 | } 64 | 65 | // Commit commit transaction that previously started. 66 | func (a *Adapter) Commit() bool { 67 | if a.tx == nil { 68 | a.GetContext().Error("Db.Commit not in transaction") 69 | return false 70 | } else { 71 | if e := a.tx.Commit(); e != nil { 72 | a.GetContext().Error("Db.Commit error, " + e.Error()) 73 | return false 74 | } 75 | return true 76 | } 77 | } 78 | 79 | // Rollback roll back transaction that previously started. 80 | func (a *Adapter) Rollback() bool { 81 | if a.tx == nil { 82 | a.GetContext().Error("Db.Rollback not in transaction") 83 | return false 84 | } else { 85 | if e := a.tx.Rollback(); e != nil { 86 | a.GetContext().Error("Db.Rollback error, " + e.Error()) 87 | return false 88 | } 89 | return true 90 | } 91 | } 92 | 93 | // InTransaction check if adapter is in transaction. 94 | func (a *Adapter) InTransaction() bool { 95 | return a.tx != nil 96 | } 97 | 98 | // QueryOne perform one row query using a default timeout context, 99 | // and always returns a non-nil value, Errors are deferred until 100 | // Row's Scan method is called. 101 | func (a *Adapter) QueryOne(query string, args ...interface{}) *Row { 102 | ctx, _ := context.WithTimeout(context.Background(), defaultTimeout) 103 | return a.QueryOneContext(ctx, query, args...) 104 | } 105 | 106 | // QueryOneContext perform one row query using a specified context, 107 | // and always returns a non-nil value, Errors are deferred until 108 | // Row's Scan method is called. 109 | func (a *Adapter) QueryOneContext(ctx context.Context, query string, args ...interface{}) *Row { 110 | start := time.Now() 111 | defer func() { 112 | elapse := time.Since(start) 113 | a.ProfileAdd("Db.QueryOne", elapse, query, args...) 114 | 115 | if elapse >= a.client.slowLogTime && a.client.slowLogTime > 0 { 116 | a.GetContext().Warn("Db.QueryOne slow, elapse:%s, query:%s, args:%v", elapse, query, args) 117 | } 118 | }() 119 | 120 | var row *sql.Row 121 | if a.tx != nil { 122 | row = a.tx.QueryRowContext(ctx, query, args...) 123 | } else { 124 | row = a.GetDb(false).QueryRowContext(ctx, query, args...) 125 | } 126 | 127 | // wrap row for profile purpose 128 | rowWrapper := rowPool.Get().(*Row) 129 | rowWrapper.SetContext(a.GetContext()) 130 | rowWrapper.row = row 131 | rowWrapper.query = query 132 | rowWrapper.args = args 133 | 134 | return rowWrapper 135 | } 136 | 137 | // Query perform query using a default timeout context. 138 | func (a *Adapter) Query(query string, args ...interface{}) *sql.Rows { 139 | ctx, _ := context.WithTimeout(context.Background(), defaultTimeout) 140 | return a.QueryContext(ctx, query, args...) 141 | } 142 | 143 | // QueryContext perform query using a specified context. 144 | func (a *Adapter) QueryContext(ctx context.Context, query string, args ...interface{}) *sql.Rows { 145 | start := time.Now() 146 | defer func() { 147 | elapse := time.Since(start) 148 | a.ProfileAdd("Db.Query", elapse, query, args...) 149 | 150 | if elapse >= a.client.slowLogTime && a.client.slowLogTime > 0 { 151 | a.GetContext().Warn("Db.Query slow, elapse:%s, query:%s, args:%v", elapse, query, args) 152 | } 153 | }() 154 | 155 | var rows *sql.Rows 156 | var err error 157 | 158 | if a.tx != nil { 159 | rows, err = a.tx.QueryContext(ctx, query, args...) 160 | } else { 161 | rows, err = a.GetDb(false).QueryContext(ctx, query, args...) 162 | } 163 | 164 | if err != nil { 165 | a.GetContext().Error("Db.Query error, %s, query:%s, args:%v", err.Error(), query, args) 166 | return nil 167 | } 168 | 169 | return rows 170 | } 171 | 172 | // Exec perform exec using a default timeout context. 173 | func (a *Adapter) Exec(query string, args ...interface{}) sql.Result { 174 | ctx, _ := context.WithTimeout(context.Background(), defaultTimeout) 175 | return a.ExecContext(ctx, query, args...) 176 | } 177 | 178 | // ExecContext perform exec using a specified context. 179 | func (a *Adapter) ExecContext(ctx context.Context, query string, args ...interface{}) sql.Result { 180 | start := time.Now() 181 | defer func() { 182 | elapse := time.Since(start) 183 | a.ProfileAdd("Db.Exec", elapse, query, args...) 184 | 185 | if elapse >= a.client.slowLogTime && a.client.slowLogTime > 0 { 186 | a.GetContext().Warn("Db.Exec slow, elapse:%s, query:%s, args:%v", elapse, query, args) 187 | } 188 | }() 189 | 190 | var res sql.Result 191 | var err error 192 | 193 | if a.tx != nil { 194 | res, err = a.tx.ExecContext(ctx, query, args...) 195 | } else { 196 | res, err = a.GetDb(true).ExecContext(ctx, query, args...) 197 | } 198 | 199 | if err != nil { 200 | a.GetContext().Error("Db.Exec error, %s, query:%s, args:%v", err.Error(), query, args) 201 | return nil 202 | } 203 | 204 | return res 205 | } 206 | 207 | // Prepare creates a prepared statement for later queries or executions, 208 | // the Close method must be called by caller. 209 | func (a *Adapter) Prepare(query string) *Stmt { 210 | ctx, _ := context.WithTimeout(context.Background(), defaultTimeout) 211 | return a.PrepareContext(ctx, query) 212 | } 213 | 214 | // PrepareContext creates a prepared statement for later queries or executions, 215 | // the Close method must be called by caller. 216 | func (a *Adapter) PrepareContext(ctx context.Context, query string) *Stmt { 217 | var stmt *sql.Stmt 218 | var err error 219 | 220 | if a.tx != nil { 221 | stmt, err = a.tx.PrepareContext(ctx, query) 222 | } else { 223 | master, pos := true, strings.IndexByte(query, ' ') 224 | if pos != -1 && strings.ToUpper(query[:pos]) == "SELECT" { 225 | master = false 226 | } 227 | 228 | stmt, err = a.GetDb(master).PrepareContext(ctx, query) 229 | } 230 | 231 | if err != nil { 232 | a.GetContext().Error("Db.Prepare error, %s, query:%s", err.Error(), query) 233 | return nil 234 | } 235 | 236 | // wrap stmt for profile purpose 237 | stmtWrapper := stmtPool.Get().(*Stmt) 238 | stmtWrapper.SetContext(a.GetContext()) 239 | stmtWrapper.stmt = stmt 240 | stmtWrapper.client = a.client 241 | stmtWrapper.query = query 242 | 243 | return stmtWrapper 244 | } 245 | 246 | func (a *Adapter) ProfileAdd(key string, elapse time.Duration, query string, args ...interface{}) { 247 | newKey := key 248 | if a.client.sqlLog == true { 249 | if len(args) > 0 { 250 | for _, v := range args { 251 | query = strings.Replace(query, "?", Util.ToString(v), 1) 252 | } 253 | } 254 | 255 | newKey = key + "(" + query + ")" 256 | } 257 | 258 | a.GetContext().ProfileAdd(newKey, elapse) 259 | } 260 | -------------------------------------------------------------------------------- /Application.go: -------------------------------------------------------------------------------- 1 | package pgo 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "reflect" 9 | "runtime" 10 | "strings" 11 | "sync" 12 | ) 13 | 14 | // Application the pgo app, 15 | // initialization steps: 16 | // 1. import pgo: pgo.init() 17 | // 2. customize app: optional 18 | // 3. pgo.Run(): serve 19 | // 20 | // configuration: 21 | // name: "app-name" 22 | // GOMAXPROCS: 2 23 | // runtimePath: "@app/runtime" 24 | // publicPath: "@app/public" 25 | // viewPath: "@viewPath" 26 | // container: {} 27 | // server: {} 28 | // components: {} 29 | type Application struct { 30 | mode int // running mode, WEB or CMD 31 | env string // running env, eg. develop/online/testing-dev/testing-qa 32 | name string // application name 33 | basePath string // base path of application 34 | runtimePath string // runtime path for log etc. 35 | publicPath string // public path for web assets 36 | viewPath string // path for view template 37 | 38 | config *Config 39 | container *Container 40 | server *Server 41 | components map[string]interface{} 42 | lock sync.RWMutex 43 | router *Router 44 | log *Log 45 | status *Status 46 | i18n *I18n 47 | view *View 48 | stopBefore *StopBefore // 服务停止前执行 [{"obj":"func"}] 49 | } 50 | 51 | func (app *Application) getBasePath(exeDir string) string { 52 | basePath := os.Getenv("PgoTestAppBasePath") 53 | if basePath == "" { 54 | basePath, _ = filepath.Abs(filepath.Join(exeDir, "..")) 55 | } 56 | 57 | return basePath 58 | } 59 | 60 | func (app *Application) Construct() { 61 | exeBase := filepath.Base(os.Args[0]) 62 | exeExt := filepath.Ext(os.Args[0]) 63 | exeDir := filepath.Dir(os.Args[0]) 64 | 65 | app.env = DefaultEnv 66 | app.mode = ModeWeb 67 | app.name = strings.TrimSuffix(exeBase, exeExt) 68 | app.basePath = app.getBasePath(exeDir) 69 | app.config = &Config{} 70 | app.container = &Container{} 71 | app.server = &Server{} 72 | app.components = make(map[string]interface{}) 73 | app.stopBefore = &StopBefore{} 74 | } 75 | 76 | func (app *Application) Init() { 77 | env := flag.String("env", "", "set running env, eg. --env online") 78 | cmd := flag.String("cmd", "", "set running cmd, eg. --cmd /foo/bar") 79 | base := flag.String("base", "", "set base path, eg. --base /base/path") 80 | flag.Parse() 81 | 82 | // overwrite running env 83 | if len(*env) > 0 { 84 | app.env = *env 85 | } 86 | 87 | // overwrite running mode 88 | if len(*cmd) > 0 { 89 | app.mode = ModeCmd 90 | } 91 | 92 | // overwrite base path 93 | if len(*base) > 0 { 94 | app.basePath, _ = filepath.Abs(*base) 95 | } 96 | 97 | // set basic path alias 98 | type dummy struct{} 99 | pkgPath := reflect.TypeOf(dummy{}).PkgPath() 100 | SetAlias("@app", app.basePath) 101 | SetAlias("@pgo", strings.TrimPrefix(pkgPath, VendorPrefix)) 102 | 103 | // initialize config object 104 | ConstructAndInit(app.config, nil) 105 | 106 | // initialize container object 107 | cntConf, _ := app.config.Get("app.container").(map[string]interface{}) 108 | ConstructAndInit(app.container, cntConf) 109 | 110 | // initialize server object 111 | svrConf, _ := app.config.Get("app.server").(map[string]interface{}) 112 | ConstructAndInit(app.server, svrConf) 113 | 114 | // overwrite app name 115 | if name := app.config.GetString("app.name", ""); len(name) > 0 { 116 | app.name = name 117 | } 118 | 119 | // overwrite GOMAXPROCS 120 | if n := app.config.GetInt("app.GOMAXPROCS", 0); n > 0 { 121 | runtime.GOMAXPROCS(n) 122 | } 123 | 124 | // set runtime path 125 | runtimePath := app.config.GetString("app.runtimePath", "@app/runtime") 126 | app.runtimePath, _ = filepath.Abs(GetAlias(runtimePath)) 127 | SetAlias("@runtime", app.runtimePath) 128 | 129 | // set public path 130 | publicPath := app.config.GetString("app.publicPath", "@app/public") 131 | app.publicPath, _ = filepath.Abs(GetAlias(publicPath)) 132 | SetAlias("@public", app.publicPath) 133 | 134 | // set view path 135 | viewPath := app.config.GetString("app.viewPath", "@app/view") 136 | app.viewPath, _ = filepath.Abs(GetAlias(viewPath)) 137 | SetAlias("@view", app.viewPath) 138 | 139 | // set core components 140 | for id, class := range app.coreComponents() { 141 | key := fmt.Sprintf("app.components.%s.class", id) 142 | app.config.Set(key, class) 143 | } 144 | 145 | // create runtime directory if not exists 146 | if _, e := os.Stat(app.runtimePath); os.IsNotExist(e) { 147 | if e := os.MkdirAll(app.runtimePath, 0755); e != nil { 148 | panic(fmt.Sprintf("failed to create %s, %s", app.runtimePath, e)) 149 | } 150 | } 151 | } 152 | 153 | // GetMode get running mode, web:1, cmd:2 154 | func (app *Application) GetMode() int { 155 | return app.mode 156 | } 157 | 158 | // GetEnv get running env 159 | func (app *Application) GetEnv() string { 160 | return app.env 161 | } 162 | 163 | // GetName get app name, default is executable name 164 | func (app *Application) GetName() string { 165 | return app.name 166 | } 167 | 168 | // GetBasePath get base path, default is parent of executable 169 | func (app *Application) GetBasePath() string { 170 | return app.basePath 171 | } 172 | 173 | // GetRuntimePath get runtime path, default is @app/runtime 174 | func (app *Application) GetRuntimePath() string { 175 | return app.runtimePath 176 | } 177 | 178 | // GetPublicPath get public path, default is @app/public 179 | func (app *Application) GetPublicPath() string { 180 | return app.publicPath 181 | } 182 | 183 | // GetViewPath get view path, default is @app/view 184 | func (app *Application) GetViewPath() string { 185 | return app.viewPath 186 | } 187 | 188 | // GetConfig get config component 189 | func (app *Application) GetConfig() *Config { 190 | return app.config 191 | } 192 | 193 | // GetContainer get container component 194 | func (app *Application) GetContainer() *Container { 195 | return app.container 196 | } 197 | 198 | // GetServer get server component 199 | func (app *Application) GetServer() *Server { 200 | return app.server 201 | } 202 | 203 | // GetRouter get router component 204 | func (app *Application) GetRouter() *Router { 205 | if app.router == nil { 206 | app.router = app.Get("router").(*Router) 207 | } 208 | 209 | return app.router 210 | } 211 | 212 | // GetLog get log component 213 | func (app *Application) GetLog() *Log { 214 | if app.log == nil { 215 | app.log = app.Get("log").(*Log) 216 | } 217 | 218 | return app.log 219 | } 220 | 221 | // GetStatus get status component 222 | func (app *Application) GetStatus() *Status { 223 | if app.status == nil { 224 | app.status = app.Get("status").(*Status) 225 | } 226 | 227 | return app.status 228 | } 229 | 230 | // GetI18n get i18n component 231 | func (app *Application) GetI18n() *I18n { 232 | if app.i18n == nil { 233 | app.i18n = app.Get("i18n").(*I18n) 234 | } 235 | 236 | return app.i18n 237 | } 238 | 239 | // GetView get view component 240 | func (app *Application) GetView() *View { 241 | if app.view == nil { 242 | app.view = app.Get("view").(*View) 243 | } 244 | 245 | return app.view 246 | } 247 | 248 | // GetStopBefore get stopBefore component 249 | func (app *Application) GetStopBefore() *StopBefore { 250 | return app.stopBefore 251 | } 252 | 253 | // Get get component by id 254 | func (app *Application) Get(id string) interface{} { 255 | if _, ok := app.components[id]; !ok { 256 | app.loadComponent(id) 257 | } 258 | 259 | app.lock.RLock() 260 | defer app.lock.RUnlock() 261 | 262 | return app.components[id] 263 | } 264 | 265 | func (app *Application) loadComponent(id string) { 266 | app.lock.Lock() 267 | defer app.lock.Unlock() 268 | 269 | // avoid repeated loading 270 | if _, ok := app.components[id]; ok { 271 | return 272 | } 273 | 274 | conf := app.config.Get("app.components." + id) 275 | if conf == nil { 276 | panic("component not found: " + id) 277 | } 278 | 279 | app.components[id] = CreateObject(conf) 280 | } 281 | 282 | func (app *Application) coreComponents() map[string]string { 283 | return map[string]string{ 284 | "router": "@pgo/Router", 285 | "log": "@pgo/Log", 286 | "status": "@pgo/Status", 287 | "i18n": "@pgo/I18n", 288 | "view": "@pgo/View", 289 | "gzip": "@pgo/Gzip", 290 | "file": "@pgo/File", 291 | 292 | "http": "@pgo/Client/Http/Client", 293 | } 294 | } 295 | --------------------------------------------------------------------------------