Merge branch 'astaxie/develop' into develop
This commit is contained in:
		
						commit
						1642cbd420
					
				
							
								
								
									
										11
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								.travis.yml
									
									
									
									
									
								
							| @ -14,6 +14,11 @@ env: | ||||
|   - ORM_DRIVER=sqlite3   ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db | ||||
|   - ORM_DRIVER=mysql    ORM_SOURCE="root:@/orm_test?charset=utf8" | ||||
|   - ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable" | ||||
| before_install: | ||||
|  - git clone git://github.com/ideawu/ssdb.git | ||||
|  - cd ssdb | ||||
|  - make | ||||
|  - cd .. | ||||
| install: | ||||
|   - go get github.com/lib/pq | ||||
|   - go get github.com/go-sql-driver/mysql | ||||
| @ -28,10 +33,16 @@ install: | ||||
|   - go get github.com/siddontang/ledisdb/ledis | ||||
|   - go get golang.org/x/tools/cmd/vet | ||||
|   - go get github.com/golang/lint/golint | ||||
|   - go get github.com/ssdb/gossdb/ssdb | ||||
| before_script: | ||||
|   - sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi" | ||||
|   - sh -c "if [ '$ORM_DRIVER' = 'mysql' ]; then mysql -u root -e 'create database orm_test;'; fi" | ||||
|   - sh -c "if [ '$ORM_DRIVER' = 'sqlite' ]; then touch $TRAVIS_BUILD_DIR/orm_test.db; fi" | ||||
|   - mkdir -p res/var | ||||
|   - ./ssdb/ssdb-server ./ssdb/ssdb.conf -d | ||||
| after_script: | ||||
|   -killall -w ssdb-server | ||||
|   - rm -rf ./res/var/* | ||||
| script: | ||||
|   - go vet -x ./... | ||||
|   - $HOME/gopath/bin/golint ./... | ||||
|  | ||||
							
								
								
									
										2
									
								
								beego.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								beego.go
									
									
									
									
									
								
							| @ -23,7 +23,7 @@ import ( | ||||
| 
 | ||||
| const ( | ||||
| 	// VERSION represent beego web framework version. | ||||
| 	VERSION = "1.6.0" | ||||
| 	VERSION = "1.6.1" | ||||
| 
 | ||||
| 	// DEV is for develop | ||||
| 	DEV = "dev" | ||||
|  | ||||
							
								
								
									
										240
									
								
								cache/ssdb/ssdb.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								cache/ssdb/ssdb.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,240 @@ | ||||
| package ssdb | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/ssdb/gossdb/ssdb" | ||||
| 
 | ||||
| 	"github.com/astaxie/beego/cache" | ||||
| ) | ||||
| 
 | ||||
| // Cache SSDB adapter | ||||
| type Cache struct { | ||||
| 	conn     *ssdb.Client | ||||
| 	conninfo []string | ||||
| } | ||||
| 
 | ||||
| //NewSsdbCache create new ssdb adapter. | ||||
| func NewSsdbCache() cache.Cache { | ||||
| 	return &Cache{} | ||||
| } | ||||
| 
 | ||||
| // Get get value from memcache. | ||||
| func (rc *Cache) Get(key string) interface{} { | ||||
| 	if rc.conn == nil { | ||||
| 		if err := rc.connectInit(); err != nil { | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 	value, err := rc.conn.Get(key) | ||||
| 	if err == nil { | ||||
| 		return value | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // GetMulti get value from memcache. | ||||
| func (rc *Cache) GetMulti(keys []string) []interface{} { | ||||
| 	size := len(keys) | ||||
| 	var values []interface{} | ||||
| 	if rc.conn == nil { | ||||
| 		if err := rc.connectInit(); err != nil { | ||||
| 			for i := 0; i < size; i++ { | ||||
| 				values = append(values, err) | ||||
| 			} | ||||
| 			return values | ||||
| 		} | ||||
| 	} | ||||
| 	res, err := rc.conn.Do("multi_get", keys) | ||||
| 	resSize := len(res) | ||||
| 	if err == nil { | ||||
| 		for i := 1; i < resSize; i += 2 { | ||||
| 			values = append(values, string(res[i+1])) | ||||
| 		} | ||||
| 		return values | ||||
| 	} | ||||
| 	for i := 0; i < size; i++ { | ||||
| 		values = append(values, err) | ||||
| 	} | ||||
| 	return values | ||||
| } | ||||
| 
 | ||||
| // DelMulti get value from memcache. | ||||
| func (rc *Cache) DelMulti(keys []string) error { | ||||
| 	if rc.conn == nil { | ||||
| 		if err := rc.connectInit(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	_, err := rc.conn.Do("multi_del", keys) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Put put value to memcache. only support string. | ||||
| func (rc *Cache) Put(key string, value interface{}, timeout time.Duration) error { | ||||
| 	if rc.conn == nil { | ||||
| 		if err := rc.connectInit(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	v, ok := value.(string) | ||||
| 	if !ok { | ||||
| 		return errors.New("value must string") | ||||
| 	} | ||||
| 	var resp []string | ||||
| 	var err error | ||||
| 	ttl := int(timeout / time.Second) | ||||
| 	if ttl < 0 { | ||||
| 		resp, err = rc.conn.Do("set", key, v) | ||||
| 	} else { | ||||
| 		resp, err = rc.conn.Do("setx", key, v, ttl) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if len(resp) == 2 && resp[0] == "ok" { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return errors.New("bad response") | ||||
| } | ||||
| 
 | ||||
| // Delete delete value in memcache. | ||||
| func (rc *Cache) Delete(key string) error { | ||||
| 	if rc.conn == nil { | ||||
| 		if err := rc.connectInit(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	_, err := rc.conn.Del(key) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Incr increase counter. | ||||
| func (rc *Cache) Incr(key string) error { | ||||
| 	if rc.conn == nil { | ||||
| 		if err := rc.connectInit(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	_, err := rc.conn.Do("incr", key, 1) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // Decr decrease counter. | ||||
| func (rc *Cache) Decr(key string) error { | ||||
| 	if rc.conn == nil { | ||||
| 		if err := rc.connectInit(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	_, err := rc.conn.Do("incr", key, -1) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // IsExist check value exists in memcache. | ||||
| func (rc *Cache) IsExist(key string) bool { | ||||
| 	if rc.conn == nil { | ||||
| 		if err := rc.connectInit(); err != nil { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	resp, err := rc.conn.Do("exists", key) | ||||
| 	if err != nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	if resp[1] == "1" { | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // ClearAll clear all cached in memcache. | ||||
| func (rc *Cache) ClearAll() error { | ||||
| 	if rc.conn == nil { | ||||
| 		if err := rc.connectInit(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	keyStart, keyEnd, limit := "", "", 50 | ||||
| 	resp, err := rc.Scan(keyStart, keyEnd, limit) | ||||
| 	for err == nil { | ||||
| 		size := len(resp) | ||||
| 		if size == 1 { | ||||
| 			return nil | ||||
| 		} | ||||
| 		keys := []string{} | ||||
| 		for i := 1; i < size; i += 2 { | ||||
| 			keys = append(keys, string(resp[i])) | ||||
| 		} | ||||
| 		_, e := rc.conn.Do("multi_del", keys) | ||||
| 		if e != nil { | ||||
| 			return e | ||||
| 		} | ||||
| 		keyStart = resp[size-2] | ||||
| 		resp, err = rc.Scan(keyStart, keyEnd, limit) | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // Scan key all cached in ssdb. | ||||
| func (rc *Cache) Scan(keyStart string, keyEnd string, limit int) ([]string, error) { | ||||
| 	if rc.conn == nil { | ||||
| 		if err := rc.connectInit(); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	resp, err := rc.conn.Do("scan", keyStart, keyEnd, limit) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return resp, nil | ||||
| } | ||||
| 
 | ||||
| // StartAndGC start memcache adapter. | ||||
| // config string is like {"conn":"connection info"}. | ||||
| // if connecting error, return. | ||||
| func (rc *Cache) StartAndGC(config string) error { | ||||
| 	var cf map[string]string | ||||
| 	json.Unmarshal([]byte(config), &cf) | ||||
| 	if _, ok := cf["conn"]; !ok { | ||||
| 		return errors.New("config has no conn key") | ||||
| 	} | ||||
| 	rc.conninfo = strings.Split(cf["conn"], ";") | ||||
| 	if rc.conn == nil { | ||||
| 		if err := rc.connectInit(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // connect to memcache and keep the connection. | ||||
| func (rc *Cache) connectInit() error { | ||||
| 	conninfoArray := strings.Split(rc.conninfo[0], ":") | ||||
| 	host := conninfoArray[0] | ||||
| 	port, e := strconv.Atoi(conninfoArray[1]) | ||||
| 	if e != nil { | ||||
| 		return e | ||||
| 	} | ||||
| 	var err error | ||||
| 	rc.conn, err = ssdb.Connect(host, port) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func init() { | ||||
| 	cache.Register("ssdb", NewSsdbCache) | ||||
| } | ||||
							
								
								
									
										103
									
								
								cache/ssdb/ssdb_test.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								cache/ssdb/ssdb_test.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,103 @@ | ||||
| package ssdb | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/astaxie/beego/cache" | ||||
| 	"strconv" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| func TestSsdbcacheCache(t *testing.T) { | ||||
| 	ssdb, err := cache.NewCache("ssdb", `{"conn": "127.0.0.1:8888"}`) | ||||
| 	if err != nil { | ||||
| 		t.Error("init err") | ||||
| 	} | ||||
| 
 | ||||
| 	// test put and exist | ||||
| 	if ssdb.IsExist("ssdb") { | ||||
| 		t.Error("check err") | ||||
| 	} | ||||
| 	timeoutDuration := 10 * time.Second | ||||
| 	//timeoutDuration := -10*time.Second   if timeoutDuration is negtive,it means permanent | ||||
| 	if err = ssdb.Put("ssdb", "ssdb", timeoutDuration); err != nil { | ||||
| 		t.Error("set Error", err) | ||||
| 	} | ||||
| 	if !ssdb.IsExist("ssdb") { | ||||
| 		t.Error("check err") | ||||
| 	} | ||||
| 
 | ||||
| 	// Get test done | ||||
| 	if err = ssdb.Put("ssdb", "ssdb", timeoutDuration); err != nil { | ||||
| 		t.Error("set Error", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if v := ssdb.Get("ssdb"); v != "ssdb" { | ||||
| 		t.Error("get Error") | ||||
| 	} | ||||
| 
 | ||||
| 	//inc/dec test done | ||||
| 	if err = ssdb.Put("ssdb", "2", timeoutDuration); err != nil { | ||||
| 		t.Error("set Error", err) | ||||
| 	} | ||||
| 	if err = ssdb.Incr("ssdb"); err != nil { | ||||
| 		t.Error("incr Error", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if v, err := strconv.Atoi(ssdb.Get("ssdb").(string)); err != nil || v != 3 { | ||||
| 		t.Error("get err") | ||||
| 	} | ||||
| 
 | ||||
| 	if err = ssdb.Decr("ssdb"); err != nil { | ||||
| 		t.Error("decr error") | ||||
| 	} | ||||
| 
 | ||||
| 	// test del | ||||
| 	if err = ssdb.Put("ssdb", "3", timeoutDuration); err != nil { | ||||
| 		t.Error("set Error", err) | ||||
| 	} | ||||
| 	if v, err := strconv.Atoi(ssdb.Get("ssdb").(string)); err != nil || v != 3 { | ||||
| 		t.Error("get err") | ||||
| 	} | ||||
| 	if err := ssdb.Delete("ssdb"); err == nil { | ||||
| 		if ssdb.IsExist("ssdb") { | ||||
| 			t.Error("delete err") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	//test string | ||||
| 	if err = ssdb.Put("ssdb", "ssdb", -10*time.Second); err != nil { | ||||
| 		t.Error("set Error", err) | ||||
| 	} | ||||
| 	if !ssdb.IsExist("ssdb") { | ||||
| 		t.Error("check err") | ||||
| 	} | ||||
| 	if v := ssdb.Get("ssdb").(string); v != "ssdb" { | ||||
| 		t.Error("get err") | ||||
| 	} | ||||
| 
 | ||||
| 	//test GetMulti done | ||||
| 	if err = ssdb.Put("ssdb1", "ssdb1", -10*time.Second); err != nil { | ||||
| 		t.Error("set Error", err) | ||||
| 	} | ||||
| 	if !ssdb.IsExist("ssdb1") { | ||||
| 		t.Error("check err") | ||||
| 	} | ||||
| 	vv := ssdb.GetMulti([]string{"ssdb", "ssdb1"}) | ||||
| 	if len(vv) != 2 { | ||||
| 		t.Error("getmulti error") | ||||
| 	} | ||||
| 	if vv[0].(string) != "ssdb" { | ||||
| 		t.Error("getmulti error") | ||||
| 	} | ||||
| 	if vv[1].(string) != "ssdb1" { | ||||
| 		t.Error("getmulti error") | ||||
| 	} | ||||
| 
 | ||||
| 	// test clear all done | ||||
| 	if err = ssdb.ClearAll(); err != nil { | ||||
| 		t.Error("clear all err") | ||||
| 	} | ||||
| 	if ssdb.IsExist("ssdb") || ssdb.IsExist("ssdb1") { | ||||
| 		t.Error("check err") | ||||
| 	} | ||||
| } | ||||
| @ -16,7 +16,6 @@ package beego | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"html/template" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| @ -106,8 +105,6 @@ var ( | ||||
| 	AppConfig *beegoAppConfig | ||||
| 	// AppPath is the absolute path to the app | ||||
| 	AppPath string | ||||
| 	// TemplateCache stores template caching | ||||
| 	TemplateCache map[string]*template.Template | ||||
| 	// GlobalSessions is the instance for the session manager | ||||
| 	GlobalSessions *session.Manager | ||||
| 
 | ||||
| @ -188,7 +185,9 @@ func init() { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	parseConfig(appConfigPath) | ||||
| 	if err := parseConfig(appConfigPath); err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // now only support ini, next will support json. | ||||
|  | ||||
| @ -46,12 +46,16 @@ func (c *fakeConfigContainer) DefaultString(key string, defaultval string) strin | ||||
| } | ||||
| 
 | ||||
| func (c *fakeConfigContainer) Strings(key string) []string { | ||||
| 	return strings.Split(c.getData(key), ";") | ||||
| 	v := c.getData(key) | ||||
| 	if v == "" { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return strings.Split(v, ";") | ||||
| } | ||||
| 
 | ||||
| func (c *fakeConfigContainer) DefaultStrings(key string, defaultval []string) []string { | ||||
| 	v := c.Strings(key) | ||||
| 	if len(v) == 0 { | ||||
| 	if v == nil { | ||||
| 		return defaultval | ||||
| 	} | ||||
| 	return v | ||||
|  | ||||
| @ -269,15 +269,20 @@ func (c *IniConfigContainer) DefaultString(key string, defaultval string) string | ||||
| } | ||||
| 
 | ||||
| // Strings returns the []string value for a given key. | ||||
| // Return nil if config value does not exist or is empty. | ||||
| func (c *IniConfigContainer) Strings(key string) []string { | ||||
| 	return strings.Split(c.String(key), ";") | ||||
| 	v := c.String(key) | ||||
| 	if v == "" { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return strings.Split(v, ";") | ||||
| } | ||||
| 
 | ||||
| // DefaultStrings returns the []string value for a given key. | ||||
| // if err != nil return defaltval | ||||
| func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string { | ||||
| 	v := c.Strings(key) | ||||
| 	if len(v) == 0 { | ||||
| 	if v == nil { | ||||
| 		return defaultval | ||||
| 	} | ||||
| 	return v | ||||
|  | ||||
| @ -71,6 +71,7 @@ peers = one;two;three | ||||
| 			"null":                  "", | ||||
| 			"demo2::key1":           "", | ||||
| 			"error":                 "", | ||||
| 			"emptystrings":          []string{}, | ||||
| 		} | ||||
| 	) | ||||
| 
 | ||||
|  | ||||
| @ -173,7 +173,7 @@ func (c *JSONConfigContainer) DefaultString(key string, defaultval string) strin | ||||
| func (c *JSONConfigContainer) Strings(key string) []string { | ||||
| 	stringVal := c.String(key) | ||||
| 	if stringVal == "" { | ||||
| 		return []string{} | ||||
| 		return nil | ||||
| 	} | ||||
| 	return strings.Split(c.String(key), ";") | ||||
| } | ||||
| @ -181,7 +181,7 @@ func (c *JSONConfigContainer) Strings(key string) []string { | ||||
| // DefaultStrings returns the []string value for a given key. | ||||
| // if err != nil return defaltval | ||||
| func (c *JSONConfigContainer) DefaultStrings(key string, defaultval []string) []string { | ||||
| 	if v := c.Strings(key); len(v) > 0 { | ||||
| 	if v := c.Strings(key); v != nil { | ||||
| 		return v | ||||
| 	} | ||||
| 	return defaultval | ||||
|  | ||||
| @ -174,14 +174,18 @@ func (c *ConfigContainer) DefaultString(key string, defaultval string) string { | ||||
| 
 | ||||
| // Strings returns the []string value for a given key. | ||||
| func (c *ConfigContainer) Strings(key string) []string { | ||||
| 	return strings.Split(c.String(key), ";") | ||||
| 	v := c.String(key) | ||||
| 	if v == "" { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return strings.Split(v, ";") | ||||
| } | ||||
| 
 | ||||
| // DefaultStrings returns the []string value for a given key. | ||||
| // if err != nil return defaltval | ||||
| func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string { | ||||
| 	v := c.Strings(key) | ||||
| 	if len(v) == 0 { | ||||
| 	if v == nil { | ||||
| 		return defaultval | ||||
| 	} | ||||
| 	return v | ||||
|  | ||||
| @ -82,4 +82,7 @@ func TestXML(t *testing.T) { | ||||
| 	if xmlconf.String("name") != "astaxie" { | ||||
| 		t.Fatal("get name error") | ||||
| 	} | ||||
| 	if xmlconf.Strings("emptystrings") != nil { | ||||
| 		t.Fatal("get emtpy strings error") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -211,14 +211,18 @@ func (c *ConfigContainer) DefaultString(key string, defaultval string) string { | ||||
| 
 | ||||
| // Strings returns the []string value for a given key. | ||||
| func (c *ConfigContainer) Strings(key string) []string { | ||||
| 	return strings.Split(c.String(key), ";") | ||||
| 	v := c.String(key) | ||||
| 	if v == "" { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return strings.Split(v, ";") | ||||
| } | ||||
| 
 | ||||
| // DefaultStrings returns the []string value for a given key. | ||||
| // if err != nil return defaltval | ||||
| func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string { | ||||
| 	v := c.Strings(key) | ||||
| 	if len(v) == 0 { | ||||
| 	if v == nil { | ||||
| 		return defaultval | ||||
| 	} | ||||
| 	return v | ||||
|  | ||||
| @ -79,4 +79,8 @@ func TestYaml(t *testing.T) { | ||||
| 	if yamlconf.String("name") != "astaxie" { | ||||
| 		t.Fatal("get name error") | ||||
| 	} | ||||
| 
 | ||||
| 	if yamlconf.Strings("emptystrings") != nil { | ||||
| 		t.Fatal("get emtpy strings error") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -24,11 +24,13 @@ package context | ||||
| 
 | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"bytes" | ||||
| 	"crypto/hmac" | ||||
| 	"crypto/sha1" | ||||
| 	"encoding/base64" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| @ -59,7 +61,10 @@ type Context struct { | ||||
| // Reset init Context, BeegoInput and BeegoOutput | ||||
| func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) { | ||||
| 	ctx.Request = r | ||||
| 	ctx.ResponseWriter = &Response{rw, false, 0} | ||||
| 	if ctx.ResponseWriter == nil { | ||||
| 		ctx.ResponseWriter = &Response{} | ||||
| 	} | ||||
| 	ctx.ResponseWriter.reset(rw) | ||||
| 	ctx.Input.Reset(ctx) | ||||
| 	ctx.Output.Reset(ctx) | ||||
| } | ||||
| @ -176,25 +181,43 @@ type Response struct { | ||||
| 	Status  int | ||||
| } | ||||
| 
 | ||||
| func (r *Response) reset(rw http.ResponseWriter) { | ||||
| 	r.ResponseWriter = rw | ||||
| 	r.Status = 0 | ||||
| 	r.Started = false | ||||
| } | ||||
| 
 | ||||
| // Write writes the data to the connection as part of an HTTP reply, | ||||
| // and sets `started` to true. | ||||
| // started means the response has sent out. | ||||
| func (w *Response) Write(p []byte) (int, error) { | ||||
| 	w.Started = true | ||||
| 	return w.ResponseWriter.Write(p) | ||||
| func (r *Response) Write(p []byte) (int, error) { | ||||
| 	r.Started = true | ||||
| 	return r.ResponseWriter.Write(p) | ||||
| } | ||||
| 
 | ||||
| // Copy writes the data to the connection as part of an HTTP reply, | ||||
| // and sets `started` to true. | ||||
| // started means the response has sent out. | ||||
| func (r *Response) Copy(buf *bytes.Buffer) (int64, error) { | ||||
| 	r.Started = true | ||||
| 	return io.Copy(r.ResponseWriter, buf) | ||||
| } | ||||
| 
 | ||||
| // WriteHeader sends an HTTP response header with status code, | ||||
| // and sets `started` to true. | ||||
| func (w *Response) WriteHeader(code int) { | ||||
| 	w.Status = code | ||||
| 	w.Started = true | ||||
| 	w.ResponseWriter.WriteHeader(code) | ||||
| func (r *Response) WriteHeader(code int) { | ||||
| 	if r.Status > 0 { | ||||
| 		//prevent multiple response.WriteHeader calls | ||||
| 		return | ||||
| 	} | ||||
| 	r.Status = code | ||||
| 	r.Started = true | ||||
| 	r.ResponseWriter.WriteHeader(code) | ||||
| } | ||||
| 
 | ||||
| // Hijack hijacker for http | ||||
| func (w *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) { | ||||
| 	hj, ok := w.ResponseWriter.(http.Hijacker) | ||||
| func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) { | ||||
| 	hj, ok := r.ResponseWriter.(http.Hijacker) | ||||
| 	if !ok { | ||||
| 		return nil, nil, errors.New("webserver doesn't support hijacking") | ||||
| 	} | ||||
| @ -202,15 +225,15 @@ func (w *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) { | ||||
| } | ||||
| 
 | ||||
| // Flush http.Flusher | ||||
| func (w *Response) Flush() { | ||||
| 	if f, ok := w.ResponseWriter.(http.Flusher); ok { | ||||
| func (r *Response) Flush() { | ||||
| 	if f, ok := r.ResponseWriter.(http.Flusher); ok { | ||||
| 		f.Flush() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // CloseNotify http.CloseNotifier | ||||
| func (w *Response) CloseNotify() <-chan bool { | ||||
| 	if cn, ok := w.ResponseWriter.(http.CloseNotifier); ok { | ||||
| func (r *Response) CloseNotify() <-chan bool { | ||||
| 	if cn, ok := r.ResponseWriter.(http.CloseNotifier); ok { | ||||
| 		return cn.CloseNotify() | ||||
| 	} | ||||
| 	return nil | ||||
|  | ||||
| @ -21,7 +21,6 @@ import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"html/template" | ||||
| 	"io" | ||||
| 	"mime" | ||||
| 	"net/http" | ||||
| 	"path/filepath" | ||||
| @ -57,7 +56,7 @@ func (output *BeegoOutput) Header(key, val string) { | ||||
| // Body sets response body content. | ||||
| // if EnableGzip, compress content string. | ||||
| // it sends out response body directly. | ||||
| func (output *BeegoOutput) Body(content []byte) { | ||||
| func (output *BeegoOutput) Body(content []byte) error { | ||||
| 	var encoding string | ||||
| 	var buf = &bytes.Buffer{} | ||||
| 	if output.EnableGzip { | ||||
| @ -75,7 +74,8 @@ func (output *BeegoOutput) Body(content []byte) { | ||||
| 		output.Status = 0 | ||||
| 	} | ||||
| 
 | ||||
| 	io.Copy(output.Context.ResponseWriter, buf) | ||||
| 	_, err := output.Context.ResponseWriter.Copy(buf) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // Cookie sets cookie value via given key. | ||||
| @ -97,9 +97,10 @@ func (output *BeegoOutput) Cookie(name string, value string, others ...interface | ||||
| 			maxAge = v | ||||
| 		} | ||||
| 
 | ||||
| 		if maxAge > 0 { | ||||
| 		switch { | ||||
| 		case maxAge > 0: | ||||
| 			fmt.Fprintf(&b, "; Expires=%s; Max-Age=%d", time.Now().Add(time.Duration(maxAge)*time.Second).UTC().Format(time.RFC1123), maxAge) | ||||
| 		} else { | ||||
| 		case maxAge < 0: | ||||
| 			fmt.Fprintf(&b, "; Max-Age=0") | ||||
| 		} | ||||
| 	} | ||||
| @ -186,8 +187,7 @@ func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, coding bool) e | ||||
| 	if coding { | ||||
| 		content = []byte(stringsToJSON(string(content))) | ||||
| 	} | ||||
| 	output.Body(content) | ||||
| 	return nil | ||||
| 	return output.Body(content) | ||||
| } | ||||
| 
 | ||||
| // JSONP writes jsonp to response body. | ||||
| @ -212,8 +212,7 @@ func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error { | ||||
| 	callbackContent.WriteString("(") | ||||
| 	callbackContent.Write(content) | ||||
| 	callbackContent.WriteString(");\r\n") | ||||
| 	output.Body(callbackContent.Bytes()) | ||||
| 	return nil | ||||
| 	return output.Body(callbackContent.Bytes()) | ||||
| } | ||||
| 
 | ||||
| // XML writes xml string to response body. | ||||
| @ -230,8 +229,7 @@ func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error { | ||||
| 		http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError) | ||||
| 		return err | ||||
| 	} | ||||
| 	output.Body(content) | ||||
| 	return nil | ||||
| 	return output.Body(content) | ||||
| } | ||||
| 
 | ||||
| // Download forces response for download file. | ||||
|  | ||||
| @ -185,8 +185,7 @@ func (c *Controller) Render() error { | ||||
| 		return err | ||||
| 	} | ||||
| 	c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8") | ||||
| 	c.Ctx.Output.Body(rb) | ||||
| 	return nil | ||||
| 	return c.Ctx.Output.Body(rb) | ||||
| } | ||||
| 
 | ||||
| // RenderString returns the rendered template string. Do not send out response. | ||||
| @ -197,33 +196,9 @@ func (c *Controller) RenderString() (string, error) { | ||||
| 
 | ||||
| // RenderBytes returns the bytes of rendered template string. Do not send out response. | ||||
| func (c *Controller) RenderBytes() ([]byte, error) { | ||||
| 	//if the controller has set layout, then first get the tplname's content set the content to the layout | ||||
| 	var buf bytes.Buffer | ||||
| 	if c.Layout != "" { | ||||
| 		if c.TplName == "" { | ||||
| 			c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt | ||||
| 		} | ||||
| 
 | ||||
| 		if BConfig.RunMode == DEV { | ||||
| 			buildFiles := []string{c.TplName} | ||||
| 			if c.LayoutSections != nil { | ||||
| 				for _, sectionTpl := range c.LayoutSections { | ||||
| 					if sectionTpl == "" { | ||||
| 						continue | ||||
| 					} | ||||
| 					buildFiles = append(buildFiles, sectionTpl) | ||||
| 				} | ||||
| 			} | ||||
| 			BuildTemplate(BConfig.WebConfig.ViewsPath, buildFiles...) | ||||
| 		} | ||||
| 		if _, ok := BeeTemplates[c.TplName]; !ok { | ||||
| 			panic("can't find templatefile in the path:" + c.TplName) | ||||
| 		} | ||||
| 		err := BeeTemplates[c.TplName].ExecuteTemplate(&buf, c.TplName, c.Data) | ||||
| 		if err != nil { | ||||
| 			Trace("template Execute err:", err) | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	buf, err := c.renderTemplate() | ||||
| 	//if the controller has set layout, then first get the tplName's content set the content to the layout | ||||
| 	if err == nil && c.Layout != "" { | ||||
| 		c.Data["LayoutContent"] = template.HTML(buf.String()) | ||||
| 
 | ||||
| 		if c.LayoutSections != nil { | ||||
| @ -232,11 +207,9 @@ func (c *Controller) RenderBytes() ([]byte, error) { | ||||
| 					c.Data[sectionName] = "" | ||||
| 					continue | ||||
| 				} | ||||
| 
 | ||||
| 				buf.Reset() | ||||
| 				err = BeeTemplates[sectionTpl].ExecuteTemplate(&buf, sectionTpl, c.Data) | ||||
| 				err = executeTemplate(&buf, sectionTpl, c.Data) | ||||
| 				if err != nil { | ||||
| 					Trace("template Execute err:", err) | ||||
| 					return nil, err | ||||
| 				} | ||||
| 				c.Data[sectionName] = template.HTML(buf.String()) | ||||
| @ -244,30 +217,32 @@ func (c *Controller) RenderBytes() ([]byte, error) { | ||||
| 		} | ||||
| 
 | ||||
| 		buf.Reset() | ||||
| 		err = BeeTemplates[c.Layout].ExecuteTemplate(&buf, c.Layout, c.Data) | ||||
| 		if err != nil { | ||||
| 			Trace("template Execute err:", err) | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		return buf.Bytes(), nil | ||||
| 		executeTemplate(&buf, c.Layout, c.Data) | ||||
| 	} | ||||
| 	return buf.Bytes(), err | ||||
| } | ||||
| 
 | ||||
| func (c *Controller) renderTemplate() (bytes.Buffer, error) { | ||||
| 	var buf bytes.Buffer | ||||
| 	if c.TplName == "" { | ||||
| 		c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt | ||||
| 	} | ||||
| 	if BConfig.RunMode == DEV { | ||||
| 		BuildTemplate(BConfig.WebConfig.ViewsPath, c.TplName) | ||||
| 		buildFiles := []string{c.TplName} | ||||
| 		if c.Layout != "" { | ||||
| 			buildFiles = append(buildFiles, c.Layout) | ||||
| 			if c.LayoutSections != nil { | ||||
| 				for _, sectionTpl := range c.LayoutSections { | ||||
| 					if sectionTpl == "" { | ||||
| 						continue | ||||
| 					} | ||||
| 					buildFiles = append(buildFiles, sectionTpl) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		BuildTemplate(BConfig.WebConfig.ViewsPath, buildFiles...) | ||||
| 	} | ||||
| 	if _, ok := BeeTemplates[c.TplName]; !ok { | ||||
| 		panic("can't find templatefile in the path:" + c.TplName) | ||||
| 	} | ||||
| 	buf.Reset() | ||||
| 	err := BeeTemplates[c.TplName].ExecuteTemplate(&buf, c.TplName, c.Data) | ||||
| 	if err != nil { | ||||
| 		Trace("template Execute err:", err) | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return buf.Bytes(), nil | ||||
| 	return buf, executeTemplate(&buf, c.TplName, c.Data) | ||||
| } | ||||
| 
 | ||||
| // Redirect sends the redirection response to url with status code. | ||||
| @ -286,7 +261,7 @@ func (c *Controller) Abort(code string) { | ||||
| 
 | ||||
| // CustomAbort stops controller handler and show the error data, it's similar Aborts, but support status code and body. | ||||
| func (c *Controller) CustomAbort(status int, body string) { | ||||
| 	c.Ctx.ResponseWriter.WriteHeader(status) | ||||
| 	c.Ctx.Output.Status = status | ||||
| 	// first panic from ErrorMaps, is is user defined error functions. | ||||
| 	if _, ok := ErrorMaps[body]; ok { | ||||
| 		panic(body) | ||||
|  | ||||
| @ -301,13 +301,12 @@ func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest { | ||||
| // JSONBody adds request raw body encoding by JSON. | ||||
| func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) { | ||||
| 	if b.req.Body == nil && obj != nil { | ||||
| 		buf := bytes.NewBuffer(nil) | ||||
| 		enc := json.NewEncoder(buf) | ||||
| 		if err := enc.Encode(obj); err != nil { | ||||
| 		byts, err := json.Marshal(obj) | ||||
| 		if err != nil { | ||||
| 			return b, err | ||||
| 		} | ||||
| 		b.req.Body = ioutil.NopCloser(buf) | ||||
| 		b.req.ContentLength = int64(buf.Len()) | ||||
| 		b.req.Body = ioutil.NopCloser(bytes.NewReader(byts)) | ||||
| 		b.req.ContentLength = int64(len(byts)) | ||||
| 		b.req.Header.Set("Content-Type", "application/json") | ||||
| 	} | ||||
| 	return b, nil | ||||
|  | ||||
							
								
								
									
										17
									
								
								logs/conn.go
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								logs/conn.go
									
									
									
									
									
								
							| @ -17,7 +17,6 @@ package logs | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"time" | ||||
| ) | ||||
| @ -25,7 +24,7 @@ import ( | ||||
| // connWriter implements LoggerInterface. | ||||
| // it writes messages in keep-live tcp connection. | ||||
| type connWriter struct { | ||||
| 	lg             *log.Logger | ||||
| 	lg             *logWriter | ||||
| 	innerWriter    io.WriteCloser | ||||
| 	ReconnectOnMsg bool   `json:"reconnectOnMsg"` | ||||
| 	Reconnect      bool   `json:"reconnect"` | ||||
| @ -43,8 +42,8 @@ func NewConn() Logger { | ||||
| 
 | ||||
| // Init init connection writer with json config. | ||||
| // json config only need key "level". | ||||
| func (c *connWriter) Init(jsonconfig string) error { | ||||
| 	return json.Unmarshal([]byte(jsonconfig), c) | ||||
| func (c *connWriter) Init(jsonConfig string) error { | ||||
| 	return json.Unmarshal([]byte(jsonConfig), c) | ||||
| } | ||||
| 
 | ||||
| // WriteMsg write message in connection. | ||||
| @ -53,7 +52,7 @@ func (c *connWriter) WriteMsg(when time.Time, msg string, level int) error { | ||||
| 	if level > c.Level { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if c.neddedConnectOnMsg() { | ||||
| 	if c.needToConnectOnMsg() { | ||||
| 		err := c.connect() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| @ -64,9 +63,7 @@ func (c *connWriter) WriteMsg(when time.Time, msg string, level int) error { | ||||
| 		defer c.innerWriter.Close() | ||||
| 	} | ||||
| 
 | ||||
| 	msg = formatLogTime(when) + msg | ||||
| 
 | ||||
| 	c.lg.Println(msg) | ||||
| 	c.lg.println(when, msg) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| @ -98,11 +95,11 @@ func (c *connWriter) connect() error { | ||||
| 	} | ||||
| 
 | ||||
| 	c.innerWriter = conn | ||||
| 	c.lg = log.New(conn, "", 0) | ||||
| 	c.lg = newLogWriter(conn) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (c *connWriter) neddedConnectOnMsg() bool { | ||||
| func (c *connWriter) needToConnectOnMsg() bool { | ||||
| 	if c.Reconnect { | ||||
| 		c.Reconnect = false | ||||
| 		return true | ||||
|  | ||||
| @ -16,7 +16,6 @@ package logs | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"runtime" | ||||
| 	"time" | ||||
| @ -47,7 +46,7 @@ var colors = []brush{ | ||||
| 
 | ||||
| // consoleWriter implements LoggerInterface and writes messages to terminal. | ||||
| type consoleWriter struct { | ||||
| 	lg       *log.Logger | ||||
| 	lg       *logWriter | ||||
| 	Level    int  `json:"level"` | ||||
| 	Colorful bool `json:"color"` //this filed is useful only when system's terminal supports color | ||||
| } | ||||
| @ -55,9 +54,9 @@ type consoleWriter struct { | ||||
| // NewConsole create ConsoleWriter returning as LoggerInterface. | ||||
| func NewConsole() Logger { | ||||
| 	cw := &consoleWriter{ | ||||
| 		lg:       log.New(os.Stdout, "", 0), | ||||
| 		lg:       newLogWriter(os.Stdout), | ||||
| 		Level:    LevelDebug, | ||||
| 		Colorful: true, | ||||
| 		Colorful: runtime.GOOS != "windows", | ||||
| 	} | ||||
| 	return cw | ||||
| } | ||||
| @ -80,12 +79,10 @@ func (c *consoleWriter) WriteMsg(when time.Time, msg string, level int) error { | ||||
| 	if level > c.Level { | ||||
| 		return nil | ||||
| 	} | ||||
| 	msg = formatLogTime(when) + msg | ||||
| 	if c.Colorful { | ||||
| 		c.lg.Println(colors[level](msg)) | ||||
| 	} else { | ||||
| 		c.lg.Println(msg) | ||||
| 		msg = colors[level](msg) | ||||
| 	} | ||||
| 	c.lg.println(when, msg) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										33
									
								
								logs/file.go
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								logs/file.go
									
									
									
									
									
								
							| @ -53,9 +53,11 @@ type fileLogWriter struct { | ||||
| 	Level int `json:"level"` | ||||
| 
 | ||||
| 	Perm os.FileMode `json:"perm"` | ||||
| 
 | ||||
| 	fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix | ||||
| } | ||||
| 
 | ||||
| // NewFileWriter create a FileLogWriter returning as LoggerInterface. | ||||
| // newFileWriter create a FileLogWriter returning as LoggerInterface. | ||||
| func newFileWriter() Logger { | ||||
| 	w := &fileLogWriter{ | ||||
| 		Filename: "", | ||||
| @ -89,6 +91,11 @@ func (w *fileLogWriter) Init(jsonConfig string) error { | ||||
| 	if len(w.Filename) == 0 { | ||||
| 		return errors.New("jsonconfig must have filename") | ||||
| 	} | ||||
| 	w.suffix = filepath.Ext(w.Filename) | ||||
| 	w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix) | ||||
| 	if w.suffix == "" { | ||||
| 		w.suffix = ".log" | ||||
| 	} | ||||
| 	err = w.startLogger() | ||||
| 	return err | ||||
| } | ||||
| @ -118,10 +125,9 @@ func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error { | ||||
| 	if level > w.Level { | ||||
| 		return nil | ||||
| 	} | ||||
| 	msg = formatLogTime(when) + msg + "\n" | ||||
| 
 | ||||
| 	h, d := formatTimeHeader(when) | ||||
| 	msg = string(h) + msg + "\n" | ||||
| 	if w.Rotate { | ||||
| 		d := when.Day() | ||||
| 		if w.needRotate(len(msg), d) { | ||||
| 			w.Lock() | ||||
| 			if w.needRotate(len(msg), d) { | ||||
| @ -196,7 +202,7 @@ func (w *fileLogWriter) lines() (int, error) { | ||||
| } | ||||
| 
 | ||||
| // DoRotate means it need to write file in new file. | ||||
| // new file name like xx.2013-01-01.2.log | ||||
| // new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size) | ||||
| func (w *fileLogWriter) doRotate(logTime time.Time) error { | ||||
| 	_, err := os.Lstat(w.Filename) | ||||
| 	if err != nil { | ||||
| @ -206,13 +212,13 @@ func (w *fileLogWriter) doRotate(logTime time.Time) error { | ||||
| 	// Find the next available number | ||||
| 	num := 1 | ||||
| 	fName := "" | ||||
| 	suffix := filepath.Ext(w.Filename) | ||||
| 	filenameOnly := strings.TrimSuffix(w.Filename, suffix) | ||||
| 	if suffix == "" { | ||||
| 		suffix = ".log" | ||||
| 	} | ||||
| 	for ; err == nil && num <= 999; num++ { | ||||
| 		fName = filenameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, suffix) | ||||
| 	if w.MaxLines > 0 || w.MaxSize > 0 { | ||||
| 		for ; err == nil && num <= 999; num++ { | ||||
| 			fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, w.suffix) | ||||
| 			_, err = os.Lstat(fName) | ||||
| 		} | ||||
| 	} else { | ||||
| 		fName = fmt.Sprintf("%s.%s%s", w.fileNameOnly, logTime.Format("2006-01-02"), w.suffix) | ||||
| 		_, err = os.Lstat(fName) | ||||
| 	} | ||||
| 	// return error if the last file checked still existed | ||||
| @ -250,7 +256,8 @@ func (w *fileLogWriter) deleteOldLog() { | ||||
| 		}() | ||||
| 
 | ||||
| 		if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.MaxDays) { | ||||
| 			if strings.HasPrefix(filepath.Base(path), filepath.Base(w.Filename)) { | ||||
| 			if strings.HasPrefix(filepath.Base(path), w.fileNameOnly) && | ||||
| 				strings.HasSuffix(filepath.Base(path), w.suffix) { | ||||
| 				os.Remove(path) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
							
								
								
									
										66
									
								
								logs/log.go
									
									
									
									
									
								
							
							
						
						
									
										66
									
								
								logs/log.go
									
									
									
									
									
								
							| @ -146,17 +146,25 @@ func (bl *BeeLogger) Async() *BeeLogger { | ||||
| func (bl *BeeLogger) SetLogger(adapterName string, config string) error { | ||||
| 	bl.lock.Lock() | ||||
| 	defer bl.lock.Unlock() | ||||
| 	if log, ok := adapters[adapterName]; ok { | ||||
| 		lg := log() | ||||
| 		err := lg.Init(config) | ||||
| 		if err != nil { | ||||
| 			fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error()) | ||||
| 			return err | ||||
| 
 | ||||
| 	for _, l := range bl.outputs { | ||||
| 		if l.name == adapterName { | ||||
| 			return fmt.Errorf("logs: duplicate adaptername %q (you have set this logger before)", adapterName) | ||||
| 		} | ||||
| 		bl.outputs = append(bl.outputs, &nameLogger{name: adapterName, Logger: lg}) | ||||
| 	} else { | ||||
| 	} | ||||
| 
 | ||||
| 	log, ok := adapters[adapterName] | ||||
| 	if !ok { | ||||
| 		return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName) | ||||
| 	} | ||||
| 
 | ||||
| 	lg := log() | ||||
| 	err := lg.Init(config) | ||||
| 	if err != nil { | ||||
| 		fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error()) | ||||
| 		return err | ||||
| 	} | ||||
| 	bl.outputs = append(bl.outputs, &nameLogger{name: adapterName, Logger: lg}) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| @ -412,45 +420,3 @@ func (bl *BeeLogger) flush() { | ||||
| 		l.Flush() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func formatLogTime(when time.Time) string { | ||||
| 	y, mo, d := when.Date() | ||||
| 	h, mi, s := when.Clock() | ||||
| 	//len(2006/01/02 15:03:04)==19 | ||||
| 	var buf [20]byte | ||||
| 	t := 3 | ||||
| 	for y >= 10 { | ||||
| 		p := y / 10 | ||||
| 		buf[t] = byte('0' + y - p*10) | ||||
| 		y = p | ||||
| 		t-- | ||||
| 	} | ||||
| 	buf[0] = byte('0' + y) | ||||
| 	buf[4] = '/' | ||||
| 	if mo > 9 { | ||||
| 		buf[5] = '1' | ||||
| 		buf[6] = byte('0' + mo - 9) | ||||
| 	} else { | ||||
| 		buf[5] = '0' | ||||
| 		buf[6] = byte('0' + mo) | ||||
| 	} | ||||
| 	buf[7] = '/' | ||||
| 	t = d / 10 | ||||
| 	buf[8] = byte('0' + t) | ||||
| 	buf[9] = byte('0' + d - t*10) | ||||
| 	buf[10] = ' ' | ||||
| 	t = h / 10 | ||||
| 	buf[11] = byte('0' + t) | ||||
| 	buf[12] = byte('0' + h - t*10) | ||||
| 	buf[13] = ':' | ||||
| 	t = mi / 10 | ||||
| 	buf[14] = byte('0' + t) | ||||
| 	buf[15] = byte('0' + mi - t*10) | ||||
| 	buf[16] = ':' | ||||
| 	t = s / 10 | ||||
| 	buf[17] = byte('0' + t) | ||||
| 	buf[18] = byte('0' + s - t*10) | ||||
| 	buf[19] = ' ' | ||||
| 
 | ||||
| 	return string(buf[0:]) | ||||
| } | ||||
|  | ||||
							
								
								
									
										79
									
								
								logs/logger.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								logs/logger.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,79 @@ | ||||
| // Copyright 2014 beego Author. All Rights Reserved. | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //      http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
| 
 | ||||
| package logs | ||||
| 
 | ||||
| import ( | ||||
| 	"io" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| type logWriter struct { | ||||
| 	sync.Mutex | ||||
| 	writer io.Writer | ||||
| } | ||||
| 
 | ||||
| func newLogWriter(wr io.Writer) *logWriter { | ||||
| 	return &logWriter{writer: wr} | ||||
| } | ||||
| 
 | ||||
| func (lg *logWriter) println(when time.Time, msg string) { | ||||
| 	lg.Lock() | ||||
| 	h, _ := formatTimeHeader(when) | ||||
| 	lg.writer.Write(append(append(h, msg...), '\n')) | ||||
| 	lg.Unlock() | ||||
| } | ||||
| 
 | ||||
| func formatTimeHeader(when time.Time) ([]byte, int) { | ||||
| 	y, mo, d := when.Date() | ||||
| 	h, mi, s := when.Clock() | ||||
| 	//len(2006/01/02 15:03:04)==19 | ||||
| 	var buf [20]byte | ||||
| 	t := 3 | ||||
| 	for y >= 10 { | ||||
| 		p := y / 10 | ||||
| 		buf[t] = byte('0' + y - p*10) | ||||
| 		y = p | ||||
| 		t-- | ||||
| 	} | ||||
| 	buf[0] = byte('0' + y) | ||||
| 	buf[4] = '/' | ||||
| 	if mo > 9 { | ||||
| 		buf[5] = '1' | ||||
| 		buf[6] = byte('0' + mo - 9) | ||||
| 	} else { | ||||
| 		buf[5] = '0' | ||||
| 		buf[6] = byte('0' + mo) | ||||
| 	} | ||||
| 	buf[7] = '/' | ||||
| 	t = d / 10 | ||||
| 	buf[8] = byte('0' + t) | ||||
| 	buf[9] = byte('0' + d - t*10) | ||||
| 	buf[10] = ' ' | ||||
| 	t = h / 10 | ||||
| 	buf[11] = byte('0' + t) | ||||
| 	buf[12] = byte('0' + h - t*10) | ||||
| 	buf[13] = ':' | ||||
| 	t = mi / 10 | ||||
| 	buf[14] = byte('0' + t) | ||||
| 	buf[15] = byte('0' + mi - t*10) | ||||
| 	buf[16] = ':' | ||||
| 	t = s / 10 | ||||
| 	buf[17] = byte('0' + t) | ||||
| 	buf[18] = byte('0' + s - t*10) | ||||
| 	buf[19] = ' ' | ||||
| 
 | ||||
| 	return buf[0:], d | ||||
| } | ||||
							
								
								
									
										116
									
								
								logs/multifile.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								logs/multifile.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,116 @@ | ||||
| // Copyright 2014 beego Author. All Rights Reserved. | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //      http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
| 
 | ||||
| package logs | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| // A filesLogWriter manages several fileLogWriter | ||||
| // filesLogWriter will write logs to the file in json configuration  and write the same level log to correspond file | ||||
| // means if the file name in configuration is project.log filesLogWriter will create project.error.log/project.debug.log | ||||
| // and write the error-level logs to project.error.log and write the debug-level logs to project.debug.log | ||||
| // the rotate attribute also  acts like fileLogWriter | ||||
| type multiFileLogWriter struct { | ||||
| 	writers       [LevelDebug + 1 + 1]*fileLogWriter // the last one for fullLogWriter | ||||
| 	fullLogWriter *fileLogWriter | ||||
| 	Separate      []string `json:"separate"` | ||||
| } | ||||
| 
 | ||||
| var levelNames = [...]string{"emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"} | ||||
| 
 | ||||
| // Init file logger with json config. | ||||
| // jsonConfig like: | ||||
| //	{ | ||||
| //	"filename":"logs/beego.log", | ||||
| //	"maxLines":0, | ||||
| //	"maxsize":0, | ||||
| //	"daily":true, | ||||
| //	"maxDays":15, | ||||
| //	"rotate":true, | ||||
| //  	"perm":0600, | ||||
| //	"separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"], | ||||
| //	} | ||||
| 
 | ||||
| func (f *multiFileLogWriter) Init(config string) error { | ||||
| 	writer := newFileWriter().(*fileLogWriter) | ||||
| 	err := writer.Init(config) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	f.fullLogWriter = writer | ||||
| 	f.writers[LevelDebug+1] = writer | ||||
| 
 | ||||
| 	//unmarshal "separate" field to f.Separate | ||||
| 	json.Unmarshal([]byte(config), f) | ||||
| 
 | ||||
| 	jsonMap := map[string]interface{}{} | ||||
| 	json.Unmarshal([]byte(config), &jsonMap) | ||||
| 
 | ||||
| 	for i := LevelEmergency; i < LevelDebug+1; i++ { | ||||
| 		for _, v := range f.Separate { | ||||
| 			if v == levelNames[i] { | ||||
| 				jsonMap["filename"] = f.fullLogWriter.fileNameOnly + "." + levelNames[i] + f.fullLogWriter.suffix | ||||
| 				jsonMap["level"] = i | ||||
| 				bs, _ := json.Marshal(jsonMap) | ||||
| 				writer = newFileWriter().(*fileLogWriter) | ||||
| 				writer.Init(string(bs)) | ||||
| 				f.writers[i] = writer | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (f *multiFileLogWriter) Destroy() { | ||||
| 	for i := 0; i < len(f.writers); i++ { | ||||
| 		if f.writers[i] != nil { | ||||
| 			f.writers[i].Destroy() | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (f *multiFileLogWriter) WriteMsg(when time.Time, msg string, level int) error { | ||||
| 	if f.fullLogWriter != nil { | ||||
| 		f.fullLogWriter.WriteMsg(when, msg, level) | ||||
| 	} | ||||
| 	for i := 0; i < len(f.writers)-1; i++ { | ||||
| 		if f.writers[i] != nil { | ||||
| 			if level == f.writers[i].Level { | ||||
| 				f.writers[i].WriteMsg(when, msg, level) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (f *multiFileLogWriter) Flush() { | ||||
| 	for i := 0; i < len(f.writers); i++ { | ||||
| 		if f.writers[i] != nil { | ||||
| 			f.writers[i].Flush() | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // newFilesWriter create a FileLogWriter returning as LoggerInterface. | ||||
| func newFilesWriter() Logger { | ||||
| 	return &multiFileLogWriter{} | ||||
| } | ||||
| 
 | ||||
| func init() { | ||||
| 	Register("multifile", newFilesWriter) | ||||
| } | ||||
							
								
								
									
										78
									
								
								logs/multifile_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								logs/multifile_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,78 @@ | ||||
| // Copyright 2014 beego Author. All Rights Reserved. | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //      http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
| 
 | ||||
| package logs | ||||
| 
 | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| func TestFiles_1(t *testing.T) { | ||||
| 	log := NewLogger(10000) | ||||
| 	log.SetLogger("multifile", `{"filename":"test.log","separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"]}`) | ||||
| 	log.Debug("debug") | ||||
| 	log.Informational("info") | ||||
| 	log.Notice("notice") | ||||
| 	log.Warning("warning") | ||||
| 	log.Error("error") | ||||
| 	log.Alert("alert") | ||||
| 	log.Critical("critical") | ||||
| 	log.Emergency("emergency") | ||||
| 	fns := []string{""} | ||||
| 	fns = append(fns, levelNames[0:]...) | ||||
| 	name := "test" | ||||
| 	suffix := ".log" | ||||
| 	for _, fn := range fns { | ||||
| 
 | ||||
| 		file := name + suffix | ||||
| 		if fn != "" { | ||||
| 			file = name + "." + fn + suffix | ||||
| 		} | ||||
| 		f, err := os.Open(file) | ||||
| 		if err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 		b := bufio.NewReader(f) | ||||
| 		lineNum := 0 | ||||
| 		lastLine := "" | ||||
| 		for { | ||||
| 			line, _, err := b.ReadLine() | ||||
| 			if err != nil { | ||||
| 				break | ||||
| 			} | ||||
| 			if len(line) > 0 { | ||||
| 				lastLine = string(line) | ||||
| 				lineNum++ | ||||
| 			} | ||||
| 		} | ||||
| 		var expected = 1 | ||||
| 		if fn == "" { | ||||
| 			expected = LevelDebug + 1 | ||||
| 		} | ||||
| 		if lineNum != expected { | ||||
| 			t.Fatal(file, "has", lineNum, "lines not "+strconv.Itoa(expected)+" lines") | ||||
| 		} | ||||
| 		if lineNum == 1 { | ||||
| 			if !strings.Contains(lastLine, fn) { | ||||
| 				t.Fatal(file + " " + lastLine + " not contains the log msg " + fn) | ||||
| 			} | ||||
| 		} | ||||
| 		os.Remove(file) | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
| @ -388,3 +388,10 @@ func NSNamespace(prefix string, params ...LinkNamespace) LinkNamespace { | ||||
| 		ns.Namespace(n) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // NSHandler add handler | ||||
| func NSHandler(rootpath string, h http.Handler) LinkNamespace { | ||||
| 	return func(ns *Namespace) { | ||||
| 		ns.Handler(rootpath, h) | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										14
									
								
								orm/db.go
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								orm/db.go
									
									
									
									
									
								
							| @ -113,7 +113,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val | ||||
| 	if fi.pk { | ||||
| 		_, value, _ = getExistPk(mi, ind) | ||||
| 	} else { | ||||
| 		field := ind.Field(fi.fieldIndex) | ||||
| 		field := ind.FieldByIndex(fi.fieldIndex) | ||||
| 		if fi.isFielder { | ||||
| 			f := field.Addr().Interface().(Fielder) | ||||
| 			value = f.RawValue() | ||||
| @ -517,9 +517,9 @@ func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time. | ||||
| 		if num > 0 { | ||||
| 			if mi.fields.pk.auto { | ||||
| 				if mi.fields.pk.fieldType&IsPostiveIntegerField > 0 { | ||||
| 					ind.Field(mi.fields.pk.fieldIndex).SetUint(0) | ||||
| 					ind.FieldByIndex(mi.fields.pk.fieldIndex).SetUint(0) | ||||
| 				} else { | ||||
| 					ind.Field(mi.fields.pk.fieldIndex).SetInt(0) | ||||
| 					ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(0) | ||||
| 				} | ||||
| 			} | ||||
| 			err := d.deleteRels(q, mi, []interface{}{pkValue}, tz) | ||||
| @ -859,13 +859,13 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi | ||||
| 							mmi = fi.relModelInfo | ||||
| 							field := last | ||||
| 							if last.Kind() != reflect.Invalid { | ||||
| 								field = reflect.Indirect(last.Field(fi.fieldIndex)) | ||||
| 								field = reflect.Indirect(last.FieldByIndex(fi.fieldIndex)) | ||||
| 								if field.IsValid() { | ||||
| 									d.setColsValues(mmi, &field, mmi.fields.dbcols, trefs[:len(mmi.fields.dbcols)], tz) | ||||
| 									for _, fi := range mmi.fields.fieldsReverse { | ||||
| 										if fi.inModel && fi.reverseFieldInfo.mi == lastm { | ||||
| 											if fi.reverseFieldInfo != nil { | ||||
| 												f := field.Field(fi.fieldIndex) | ||||
| 												f := field.FieldByIndex(fi.fieldIndex) | ||||
| 												if f.Kind() == reflect.Ptr { | ||||
| 													f.Set(last.Addr()) | ||||
| 												} | ||||
| @ -1014,7 +1014,7 @@ func (d *dbBase) setColsValues(mi *modelInfo, ind *reflect.Value, cols []string, | ||||
| 
 | ||||
| 		fi := mi.fields.GetByColumn(column) | ||||
| 
 | ||||
| 		field := ind.Field(fi.fieldIndex) | ||||
| 		field := ind.FieldByIndex(fi.fieldIndex) | ||||
| 
 | ||||
| 		value, err := d.convertValueFromDB(fi, val, tz) | ||||
| 		if err != nil { | ||||
| @ -1350,7 +1350,7 @@ setValue: | ||||
| 			fieldType = fi.relModelInfo.fields.pk.fieldType | ||||
| 			mf := reflect.New(fi.relModelInfo.addrField.Elem().Type()) | ||||
| 			field.Set(mf) | ||||
| 			f := mf.Elem().Field(fi.relModelInfo.fields.pk.fieldIndex) | ||||
| 			f := mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex) | ||||
| 			field = f | ||||
| 			goto setValue | ||||
| 		} | ||||
|  | ||||
| @ -59,6 +59,7 @@ var ( | ||||
| 		"postgres": DRPostgres, | ||||
| 		"sqlite3":  DRSqlite, | ||||
| 		"tidb":     DRTiDB, | ||||
| 		"oracle":   DROracle, | ||||
| 	} | ||||
| 	dbBasers = map[DriverType]dbBaser{ | ||||
| 		DRMySQL:    newdbBaseMysql(), | ||||
| @ -151,7 +152,7 @@ func detectTZ(al *alias) { | ||||
| 			al.Engine = "INNODB" | ||||
| 		} | ||||
| 
 | ||||
| 	case DRSqlite: | ||||
| 	case DRSqlite, DROracle: | ||||
| 		al.TZ = time.UTC | ||||
| 
 | ||||
| 	case DRPostgres: | ||||
|  | ||||
| @ -14,6 +14,41 @@ | ||||
| 
 | ||||
| package orm | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| // oracle operators. | ||||
| var oracleOperators = map[string]string{ | ||||
| 	"exact":       "= ?", | ||||
| 	"gt":          "> ?", | ||||
| 	"gte":         ">= ?", | ||||
| 	"lt":          "< ?", | ||||
| 	"lte":         "<= ?", | ||||
| 	"//iendswith": "LIKE ?", | ||||
| } | ||||
| 
 | ||||
| // oracle column field types. | ||||
| var oracleTypes = map[string]string{ | ||||
| 	"pk":              "NOT NULL PRIMARY KEY", | ||||
| 	"bool":            "bool", | ||||
| 	"string":          "VARCHAR2(%d)", | ||||
| 	"string-text":     "VARCHAR2(%d)", | ||||
| 	"time.Time-date":  "DATE", | ||||
| 	"time.Time":       "TIMESTAMP", | ||||
| 	"int8":            "INTEGER", | ||||
| 	"int16":           "INTEGER", | ||||
| 	"int32":           "INTEGER", | ||||
| 	"int64":           "INTEGER", | ||||
| 	"uint8":           "INTEGER", | ||||
| 	"uint16":          "INTEGER", | ||||
| 	"uint32":          "INTEGER", | ||||
| 	"uint64":          "INTEGER", | ||||
| 	"float64":         "NUMBER", | ||||
| 	"float64-decimal": "NUMBER(%d, %d)", | ||||
| } | ||||
| 
 | ||||
| // oracle dbBaser | ||||
| type dbBaseOracle struct { | ||||
| 	dbBase | ||||
| @ -27,3 +62,35 @@ func newdbBaseOracle() dbBaser { | ||||
| 	b.ins = b | ||||
| 	return b | ||||
| } | ||||
| 
 | ||||
| // OperatorSQL get oracle operator. | ||||
| func (d *dbBaseOracle) OperatorSQL(operator string) string { | ||||
| 	return oracleOperators[operator] | ||||
| } | ||||
| 
 | ||||
| // DbTypes get oracle table field types. | ||||
| func (d *dbBaseOracle) DbTypes() map[string]string { | ||||
| 	return oracleTypes | ||||
| } | ||||
| 
 | ||||
| //ShowTablesQuery show all the tables in database | ||||
| func (d *dbBaseOracle) ShowTablesQuery() string { | ||||
| 	return "SELECT TABLE_NAME FROM USER_TABLES" | ||||
| } | ||||
| 
 | ||||
| // Oracle | ||||
| func (d *dbBaseOracle) ShowColumnsQuery(table string) string { | ||||
| 	return fmt.Sprintf("SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS "+ | ||||
| 		"WHERE TABLE_NAME ='%s'", strings.ToUpper(table)) | ||||
| } | ||||
| 
 | ||||
| // check index is exist | ||||
| func (d *dbBaseOracle) IndexExists(db dbQuerier, table string, name string) bool { | ||||
| 	row := db.QueryRow("SELECT COUNT(*) FROM USER_IND_COLUMNS, USER_INDEXES "+ | ||||
| 		"WHERE USER_IND_COLUMNS.INDEX_NAME = USER_INDEXES.INDEX_NAME "+ | ||||
| 		"AND  USER_IND_COLUMNS.TABLE_NAME = ? AND USER_IND_COLUMNS.INDEX_NAME = ?", strings.ToUpper(table), strings.ToUpper(name)) | ||||
| 
 | ||||
| 	var cnt int | ||||
| 	row.Scan(&cnt) | ||||
| 	return cnt > 0 | ||||
| } | ||||
|  | ||||
| @ -32,7 +32,7 @@ func getDbAlias(name string) *alias { | ||||
| func getExistPk(mi *modelInfo, ind reflect.Value) (column string, value interface{}, exist bool) { | ||||
| 	fi := mi.fields.pk | ||||
| 
 | ||||
| 	v := ind.Field(fi.fieldIndex) | ||||
| 	v := ind.FieldByIndex(fi.fieldIndex) | ||||
| 	if fi.fieldType&IsPostiveIntegerField > 0 { | ||||
| 		vu := v.Uint() | ||||
| 		exist = vu > 0 | ||||
|  | ||||
| @ -102,7 +102,7 @@ func newFields() *fields { | ||||
| // single field info | ||||
| type fieldInfo struct { | ||||
| 	mi                  *modelInfo | ||||
| 	fieldIndex          int | ||||
| 	fieldIndex          []int | ||||
| 	fieldType           int | ||||
| 	dbcol               bool | ||||
| 	inModel             bool | ||||
| @ -138,7 +138,7 @@ type fieldInfo struct { | ||||
| } | ||||
| 
 | ||||
| // new field info | ||||
| func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField) (fi *fieldInfo, err error) { | ||||
| func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField, mName string) (fi *fieldInfo, err error) { | ||||
| 	var ( | ||||
| 		tag       string | ||||
| 		tagValue  string | ||||
| @ -278,7 +278,7 @@ checkType: | ||||
| 	fi.column = getColumnName(fieldType, addrField, sf, tags["column"]) | ||||
| 	fi.addrValue = addrField | ||||
| 	fi.sf = sf | ||||
| 	fi.fullName = mi.fullName + "." + sf.Name | ||||
| 	fi.fullName = mi.fullName + mName + "." + sf.Name | ||||
| 
 | ||||
| 	fi.null = attrs["null"] | ||||
| 	fi.index = attrs["index"] | ||||
|  | ||||
| @ -36,11 +36,6 @@ type modelInfo struct { | ||||
| 
 | ||||
| // new model info | ||||
| func newModelInfo(val reflect.Value) (info *modelInfo) { | ||||
| 	var ( | ||||
| 		err error | ||||
| 		fi  *fieldInfo | ||||
| 		sf  reflect.StructField | ||||
| 	) | ||||
| 
 | ||||
| 	info = &modelInfo{} | ||||
| 	info.fields = newFields() | ||||
| @ -53,13 +48,31 @@ func newModelInfo(val reflect.Value) (info *modelInfo) { | ||||
| 	info.name = typ.Name() | ||||
| 	info.fullName = getFullName(typ) | ||||
| 
 | ||||
| 	addModelFields(info, ind, "", []int{}) | ||||
| 
 | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func addModelFields(info *modelInfo, ind reflect.Value, mName string, index []int) { | ||||
| 	var ( | ||||
| 		err error | ||||
| 		fi  *fieldInfo | ||||
| 		sf  reflect.StructField | ||||
| 	) | ||||
| 
 | ||||
| 	for i := 0; i < ind.NumField(); i++ { | ||||
| 		field := ind.Field(i) | ||||
| 		sf = ind.Type().Field(i) | ||||
| 		if sf.PkgPath != "" { | ||||
| 			continue | ||||
| 		} | ||||
| 		fi, err = newFieldInfo(info, field, sf) | ||||
| 		// add anonymous struct fields | ||||
| 		if sf.Anonymous { | ||||
| 			addModelFields(info, field, mName+"."+sf.Name, append(index, i)) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		fi, err = newFieldInfo(info, field, sf, mName) | ||||
| 
 | ||||
| 		if err != nil { | ||||
| 			if err == errSkipField { | ||||
| @ -84,7 +97,7 @@ func newModelInfo(val reflect.Value) (info *modelInfo) { | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		fi.fieldIndex = i | ||||
| 		fi.fieldIndex = append(index, i) | ||||
| 		fi.mi = info | ||||
| 		fi.inModel = true | ||||
| 	} | ||||
| @ -93,8 +106,6 @@ func newModelInfo(val reflect.Value) (info *modelInfo) { | ||||
| 		fmt.Println(fmt.Errorf("field: %s.%s, %s", ind.Type(), sf.Name, err)) | ||||
| 		os.Exit(2) | ||||
| 	} | ||||
| 
 | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // combine related model info to new model info. | ||||
|  | ||||
| @ -25,7 +25,6 @@ import ( | ||||
| 	_ "github.com/go-sql-driver/mysql" | ||||
| 	_ "github.com/lib/pq" | ||||
| 	_ "github.com/mattn/go-sqlite3" | ||||
| 
 | ||||
| 	// As tidb can't use go get, so disable the tidb testing now | ||||
| 	// _ "github.com/pingcap/tidb" | ||||
| ) | ||||
| @ -352,6 +351,30 @@ type GroupPermissions struct { | ||||
| 	Permission *Permission `orm:"rel(fk)"` | ||||
| } | ||||
| 
 | ||||
| type ModelID struct { | ||||
| 	ID int64 | ||||
| } | ||||
| 
 | ||||
| type ModelBase struct { | ||||
| 	ModelID | ||||
| 
 | ||||
| 	Created time.Time `orm:"auto_now_add;type(datetime)"` | ||||
| 	Updated time.Time `orm:"auto_now;type(datetime)"` | ||||
| } | ||||
| 
 | ||||
| type InLine struct { | ||||
| 	// Common Fields | ||||
| 	ModelBase | ||||
| 
 | ||||
| 	// Other Fields | ||||
| 	Name  string `orm:"unique"` | ||||
| 	Email string | ||||
| } | ||||
| 
 | ||||
| func NewInLine() *InLine { | ||||
| 	return new(InLine) | ||||
| } | ||||
| 
 | ||||
| var DBARGS = struct { | ||||
| 	Driver string | ||||
| 	Source string | ||||
|  | ||||
| @ -140,7 +140,7 @@ func (o *orm) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, i | ||||
| 		return (err == nil), id, err | ||||
| 	} | ||||
| 
 | ||||
| 	return false, ind.Field(mi.fields.pk.fieldIndex).Int(), err | ||||
| 	return false, ind.FieldByIndex(mi.fields.pk.fieldIndex).Int(), err | ||||
| } | ||||
| 
 | ||||
| // insert model data to database | ||||
| @ -160,9 +160,9 @@ func (o *orm) Insert(md interface{}) (int64, error) { | ||||
| func (o *orm) setPk(mi *modelInfo, ind reflect.Value, id int64) { | ||||
| 	if mi.fields.pk.auto { | ||||
| 		if mi.fields.pk.fieldType&IsPostiveIntegerField > 0 { | ||||
| 			ind.Field(mi.fields.pk.fieldIndex).SetUint(uint64(id)) | ||||
| 			ind.FieldByIndex(mi.fields.pk.fieldIndex).SetUint(uint64(id)) | ||||
| 		} else { | ||||
| 			ind.Field(mi.fields.pk.fieldIndex).SetInt(id) | ||||
| 			ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(id) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -290,7 +290,7 @@ func (o *orm) LoadRelated(md interface{}, name string, args ...interface{}) (int | ||||
| 		qs.orders = []string{order} | ||||
| 	} | ||||
| 
 | ||||
| 	find := ind.Field(fi.fieldIndex) | ||||
| 	find := ind.FieldByIndex(fi.fieldIndex) | ||||
| 
 | ||||
| 	var nums int64 | ||||
| 	var err error | ||||
|  | ||||
| @ -51,9 +51,9 @@ func (o *insertSet) Insert(md interface{}) (int64, error) { | ||||
| 	if id > 0 { | ||||
| 		if o.mi.fields.pk.auto { | ||||
| 			if o.mi.fields.pk.fieldType&IsPostiveIntegerField > 0 { | ||||
| 				ind.Field(o.mi.fields.pk.fieldIndex).SetUint(uint64(id)) | ||||
| 				ind.FieldByIndex(o.mi.fields.pk.fieldIndex).SetUint(uint64(id)) | ||||
| 			} else { | ||||
| 				ind.Field(o.mi.fields.pk.fieldIndex).SetInt(id) | ||||
| 				ind.FieldByIndex(o.mi.fields.pk.fieldIndex).SetInt(id) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @ -342,7 +342,7 @@ func (o *rawSet) QueryRow(containers ...interface{}) error { | ||||
| 				for _, col := range columns { | ||||
| 					if fi := sMi.fields.GetByColumn(col); fi != nil { | ||||
| 						value := reflect.ValueOf(columnsMp[col]).Elem().Interface() | ||||
| 						o.setFieldValue(ind.FieldByIndex([]int{fi.fieldIndex}), value) | ||||
| 						o.setFieldValue(ind.FieldByIndex(fi.fieldIndex), value) | ||||
| 					} | ||||
| 				} | ||||
| 			} else { | ||||
| @ -480,7 +480,7 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) { | ||||
| 				for _, col := range columns { | ||||
| 					if fi := sMi.fields.GetByColumn(col); fi != nil { | ||||
| 						value := reflect.ValueOf(columnsMp[col]).Elem().Interface() | ||||
| 						o.setFieldValue(ind.FieldByIndex([]int{fi.fieldIndex}), value) | ||||
| 						o.setFieldValue(ind.FieldByIndex(fi.fieldIndex), value) | ||||
| 					} | ||||
| 				} | ||||
| 			} else { | ||||
|  | ||||
| @ -187,6 +187,7 @@ func TestSyncDb(t *testing.T) { | ||||
| 	RegisterModel(new(Group)) | ||||
| 	RegisterModel(new(Permission)) | ||||
| 	RegisterModel(new(GroupPermissions)) | ||||
| 	RegisterModel(new(InLine)) | ||||
| 
 | ||||
| 	err := RunSyncdb("default", true, Debug) | ||||
| 	throwFail(t, err) | ||||
| @ -206,6 +207,7 @@ func TestRegisterModels(t *testing.T) { | ||||
| 	RegisterModel(new(Group)) | ||||
| 	RegisterModel(new(Permission)) | ||||
| 	RegisterModel(new(GroupPermissions)) | ||||
| 	RegisterModel(new(InLine)) | ||||
| 
 | ||||
| 	BootStrap() | ||||
| 
 | ||||
| @ -1928,3 +1930,25 @@ func TestReadOrCreate(t *testing.T) { | ||||
| 
 | ||||
| 	dORM.Delete(u) | ||||
| } | ||||
| 
 | ||||
| func TestInLine(t *testing.T) { | ||||
| 	name := "inline" | ||||
| 	email := "hello@go.com" | ||||
| 	inline := NewInLine() | ||||
| 	inline.Name = name | ||||
| 	inline.Email = email | ||||
| 
 | ||||
| 	id, err := dORM.Insert(inline) | ||||
| 	throwFail(t, err) | ||||
| 	throwFail(t, AssertIs(id, 1)) | ||||
| 
 | ||||
| 	il := NewInLine() | ||||
| 	il.ID = 1 | ||||
| 	err = dORM.Read(il) | ||||
| 	throwFail(t, err) | ||||
| 
 | ||||
| 	throwFail(t, AssertIs(il.Name, name)) | ||||
| 	throwFail(t, AssertIs(il.Email, email)) | ||||
| 	throwFail(t, AssertIs(il.Created.In(DefaultTimeLoc), inline.Created.In(DefaultTimeLoc), testDate)) | ||||
| 	throwFail(t, AssertIs(il.Updated.In(DefaultTimeLoc), inline.Updated.In(DefaultTimeLoc), testDateTime)) | ||||
| } | ||||
|  | ||||
| @ -58,7 +58,7 @@ func parserPkg(pkgRealpath, pkgpath string) error { | ||||
| 	rep := strings.NewReplacer("/", "_", ".", "_") | ||||
| 	commentFilename = coomentPrefix + rep.Replace(pkgpath) + ".go" | ||||
| 	if !compareFile(pkgRealpath) { | ||||
| 		Info(pkgRealpath + " has not changed, not reloading") | ||||
| 		Info(pkgRealpath + " no changed") | ||||
| 		return nil | ||||
| 	} | ||||
| 	genInfoList = make(map[string][]ControllerComments) | ||||
|  | ||||
| @ -35,7 +35,7 @@ | ||||
| // | ||||
| //	beego.InsertFilter("*", beego.BeforeRouter,apiauth.APISecretAuth(getAppSecret, 360)) | ||||
| // | ||||
| // Infomation: | ||||
| // Information: | ||||
| // | ||||
| // In the request user should include these params in the query | ||||
| // | ||||
|  | ||||
| @ -607,6 +607,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) | ||||
| 	) | ||||
| 	context := p.pool.Get().(*beecontext.Context) | ||||
| 	context.Reset(rw, r) | ||||
| 
 | ||||
| 	defer p.pool.Put(context) | ||||
| 	defer p.recoverPanic(context) | ||||
| 
 | ||||
|  | ||||
| @ -65,6 +65,11 @@ func (tc *TestController) GetManyRouter() { | ||||
| 	tc.Ctx.WriteString(tc.Ctx.Input.Query(":id") + tc.Ctx.Input.Query(":page")) | ||||
| } | ||||
| 
 | ||||
| func (tc *TestController) GetEmptyBody() { | ||||
| 	var res []byte | ||||
| 	tc.Ctx.Output.Body(res) | ||||
| } | ||||
| 
 | ||||
| type ResStatus struct { | ||||
| 	Code int | ||||
| 	Msg  string | ||||
| @ -239,6 +244,21 @@ func TestManyRoute(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Test for issue #1669 | ||||
| func TestEmptyResponse(t *testing.T) { | ||||
| 
 | ||||
| 	r, _ := http.NewRequest("GET", "/beego-empty.html", nil) | ||||
| 	w := httptest.NewRecorder() | ||||
| 
 | ||||
| 	handler := NewControllerRegister() | ||||
| 	handler.Add("/beego-empty.html", &TestController{}, "get:GetEmptyBody") | ||||
| 	handler.ServeHTTP(w, r) | ||||
| 
 | ||||
| 	if body := w.Body.String(); body != "" { | ||||
| 		t.Error("want empty body") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestNotFound(t *testing.T) { | ||||
| 	r, _ := http.NewRequest("GET", "/", nil) | ||||
| 	w := httptest.NewRecorder() | ||||
|  | ||||
| @ -93,12 +93,14 @@ type serveContentHolder struct { | ||||
| 
 | ||||
| var ( | ||||
| 	staticFileMap = make(map[string]*serveContentHolder) | ||||
| 	mapLock       sync.Mutex | ||||
| 	mapLock       sync.RWMutex | ||||
| ) | ||||
| 
 | ||||
| func openFile(filePath string, fi os.FileInfo, acceptEncoding string) (bool, string, *serveContentHolder, error) { | ||||
| 	mapKey := acceptEncoding + ":" + filePath | ||||
| 	mapLock.RLock() | ||||
| 	mapFile, _ := staticFileMap[mapKey] | ||||
| 	mapLock.RUnlock() | ||||
| 	if isOk(mapFile, fi) { | ||||
| 		return mapFile.encoding != "", mapFile.encoding, mapFile, nil | ||||
| 	} | ||||
|  | ||||
| @ -7,8 +7,8 @@ import ( | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"testing" | ||||
| 	"path/filepath" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| var currentWorkDir, _ = os.Getwd() | ||||
|  | ||||
							
								
								
									
										35
									
								
								template.go
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								template.go
									
									
									
									
									
								
							| @ -18,6 +18,7 @@ import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"html/template" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| @ -30,13 +31,28 @@ import ( | ||||
| 
 | ||||
| var ( | ||||
| 	beegoTplFuncMap = make(template.FuncMap) | ||||
| 	// BeeTemplates caching map and supported template file extensions. | ||||
| 	BeeTemplates  = make(map[string]*template.Template) | ||||
| 	templatesLock sync.Mutex | ||||
| 	// BeeTemplateExt stores the template extension which will build | ||||
| 	BeeTemplateExt = []string{"tpl", "html"} | ||||
| 	// beeTemplates caching map and supported template file extensions. | ||||
| 	beeTemplates  = make(map[string]*template.Template) | ||||
| 	templatesLock sync.RWMutex | ||||
| 	// beeTemplateExt stores the template extension which will build | ||||
| 	beeTemplateExt = []string{"tpl", "html"} | ||||
| ) | ||||
| 
 | ||||
| func executeTemplate(wr io.Writer, name string, data interface{}) error { | ||||
| 	if BConfig.RunMode == DEV { | ||||
| 		templatesLock.RLock() | ||||
| 		defer templatesLock.RUnlock() | ||||
| 	} | ||||
| 	if t, ok := beeTemplates[name]; ok { | ||||
| 		err := t.ExecuteTemplate(wr, name, data) | ||||
| 		if err != nil { | ||||
| 			Trace("template Execute err:", err) | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
| 	panic("can't find templatefile in the path:" + name) | ||||
| } | ||||
| 
 | ||||
| func init() { | ||||
| 	beegoTplFuncMap["dateformat"] = DateFormat | ||||
| 	beegoTplFuncMap["date"] = Date | ||||
| @ -55,7 +71,6 @@ func init() { | ||||
| 	beegoTplFuncMap["config"] = GetConfig | ||||
| 	beegoTplFuncMap["map_get"] = MapGet | ||||
| 
 | ||||
| 	// go1.2 added template funcs | ||||
| 	// Comparisons | ||||
| 	beegoTplFuncMap["eq"] = eq // == | ||||
| 	beegoTplFuncMap["ge"] = ge // >= | ||||
| @ -103,7 +118,7 @@ func (tf *templateFile) visit(paths string, f os.FileInfo, err error) error { | ||||
| 
 | ||||
| // HasTemplateExt return this path contains supported template extension of beego or not. | ||||
| func HasTemplateExt(paths string) bool { | ||||
| 	for _, v := range BeeTemplateExt { | ||||
| 	for _, v := range beeTemplateExt { | ||||
| 		if strings.HasSuffix(paths, "."+v) { | ||||
| 			return true | ||||
| 		} | ||||
| @ -113,12 +128,12 @@ func HasTemplateExt(paths string) bool { | ||||
| 
 | ||||
| // AddTemplateExt add new extension for template. | ||||
| func AddTemplateExt(ext string) { | ||||
| 	for _, v := range BeeTemplateExt { | ||||
| 	for _, v := range beeTemplateExt { | ||||
| 		if v == ext { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	BeeTemplateExt = append(BeeTemplateExt, ext) | ||||
| 	beeTemplateExt = append(beeTemplateExt, ext) | ||||
| } | ||||
| 
 | ||||
| // BuildTemplate will build all template files in a directory. | ||||
| @ -149,7 +164,7 @@ func BuildTemplate(dir string, files ...string) error { | ||||
| 				if err != nil { | ||||
| 					Trace("parse template err:", file, err) | ||||
| 				} else { | ||||
| 					BeeTemplates[file] = t | ||||
| 					beeTemplates[file] = t | ||||
| 				} | ||||
| 				templatesLock.Unlock() | ||||
| 			} | ||||
|  | ||||
| @ -70,10 +70,10 @@ func TestTemplate(t *testing.T) { | ||||
| 	if err := BuildTemplate(dir); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if len(BeeTemplates) != 3 { | ||||
| 		t.Fatalf("should be 3 but got %v", len(BeeTemplates)) | ||||
| 	if len(beeTemplates) != 3 { | ||||
| 		t.Fatalf("should be 3 but got %v", len(beeTemplates)) | ||||
| 	} | ||||
| 	if err := BeeTemplates["index.tpl"].ExecuteTemplate(os.Stdout, "index.tpl", nil); err != nil { | ||||
| 	if err := beeTemplates["index.tpl"].ExecuteTemplate(os.Stdout, "index.tpl", nil); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	for _, name := range files { | ||||
| @ -126,7 +126,7 @@ func TestRelativeTemplate(t *testing.T) { | ||||
| 	if err := BuildTemplate(dir, files[1]); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if err := BeeTemplates["easyui/rbac/user.tpl"].ExecuteTemplate(os.Stdout, "easyui/rbac/user.tpl", nil); err != nil { | ||||
| 	if err := beeTemplates["easyui/rbac/user.tpl"].ExecuteTemplate(os.Stdout, "easyui/rbac/user.tpl", nil); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	for _, name := range files { | ||||
|  | ||||
							
								
								
									
										6
									
								
								tree.go
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								tree.go
									
									
									
									
									
								
							| @ -141,7 +141,7 @@ func (t *Tree) addtree(segments []string, tree *Tree, wildcards []string, reg st | ||||
| 				regexpStr = "([^.]+).(.+)" | ||||
| 				params = params[1:] | ||||
| 			} else { | ||||
| 				for _ = range params { | ||||
| 				for range params { | ||||
| 					regexpStr = "([^/]+)/" + regexpStr | ||||
| 				} | ||||
| 			} | ||||
| @ -254,7 +254,7 @@ func (t *Tree) addseg(segments []string, route interface{}, wildcards []string, | ||||
| 					regexpStr = "/([^.]+).(.+)" | ||||
| 					params = params[1:] | ||||
| 				} else { | ||||
| 					for _ = range params { | ||||
| 					for range params { | ||||
| 						regexpStr = "/([^/]+)" + regexpStr | ||||
| 					} | ||||
| 				} | ||||
| @ -389,7 +389,7 @@ type leafInfo struct { | ||||
| func (leaf *leafInfo) match(wildcardValues []string, ctx *context.Context) (ok bool) { | ||||
| 	//fmt.Println("Leaf:", wildcardValues, leaf.wildcards, leaf.regexps) | ||||
| 	if leaf.regexps == nil { | ||||
| 		if len(wildcardValues) == 0 { // static path | ||||
| 		if len(wildcardValues) == 0 && len(leaf.wildcards) == 0 { // static path | ||||
| 			return true | ||||
| 		} | ||||
| 		// match * | ||||
|  | ||||
							
								
								
									
										15
									
								
								tree_test.go
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								tree_test.go
									
									
									
									
									
								
							| @ -97,6 +97,21 @@ func TestTreeRouters(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestStaticPath(t *testing.T) { | ||||
| 	tr := NewTree() | ||||
| 	tr.AddRouter("/topic/:id", "wildcard") | ||||
| 	tr.AddRouter("/topic", "static") | ||||
| 	ctx := context.NewContext() | ||||
| 	obj := tr.Match("/topic", ctx) | ||||
| 	if obj == nil || obj.(string) != "static" { | ||||
| 		t.Fatal("/topic is  a static route") | ||||
| 	} | ||||
| 	obj = tr.Match("/topic/1", ctx) | ||||
| 	if obj == nil || obj.(string) != "wildcard" { | ||||
| 		t.Fatal("/topic/1 is a wildcard route") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestAddTree(t *testing.T) { | ||||
| 	tr := NewTree() | ||||
| 	tr.AddRouter("/shop/:id/account", "astaxie") | ||||
|  | ||||
| @ -588,7 +588,7 @@ func (b Base64) GetLimitValue() interface{} { | ||||
| } | ||||
| 
 | ||||
| // just for chinese mobile phone number | ||||
| var mobilePattern = regexp.MustCompile("^((\\+86)|(86))?(1(([35][0-9])|[8][0-9]|[7][0679]|[4][579]))\\d{8}$") | ||||
| var mobilePattern = regexp.MustCompile("^((\\+86)|(86))?(1(([35][0-9])|[8][0-9]|[7][06789]|[4][579]))\\d{8}$") | ||||
| 
 | ||||
| // Mobile check struct | ||||
| type Mobile struct { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user