From 11e6c2829bdf2eae8a674d6616a563356eaebb43 Mon Sep 17 00:00:00 2001 From: cloudaice Date: Tue, 21 Jan 2014 17:57:37 +0800 Subject: [PATCH 01/11] diffrent level logs display diffrent color --- logs/console.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/logs/console.go b/logs/console.go index c5fa2380..dd271ff1 100644 --- a/logs/console.go +++ b/logs/console.go @@ -6,6 +6,25 @@ import ( "os" ) +type Brush func(string) string + +func NewBrush(color string) Brush { + pre := "\033[" + reset := "\033[0m" + return func(text string) string { + return pre + color + "m" + text + reset + } +} + +var colors = []Brush{ + NewBrush("1;36"), // Trace cyan + NewBrush("1;34"), // Debug blue + NewBrush("1;32"), // Info green + NewBrush("1;33"), // Warn yellow + NewBrush("1;31"), // Error red + NewBrush("1;35"), // Critical purple +} + // ConsoleWriter implements LoggerInterface and writes messages to terminal. type ConsoleWriter struct { lg *log.Logger @@ -35,7 +54,7 @@ func (c *ConsoleWriter) WriteMsg(msg string, level int) error { if level < c.Level { return nil } - c.lg.Println(msg) + c.lg.Println(colors[level](msg)) return nil } From 1509a6b681950ff4ee215c83b3207ce281b8e978 Mon Sep 17 00:00:00 2001 From: Pengfei Xue Date: Tue, 21 Jan 2014 18:48:16 +0800 Subject: [PATCH 02/11] fix bug, redis session doesnt work --- session/sess_redis.go | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/session/sess_redis.go b/session/sess_redis.go index 51685844..d19f316f 100644 --- a/session/sess_redis.go +++ b/session/sess_redis.go @@ -16,7 +16,7 @@ var MAX_POOL_SIZE = 100 var redisPool chan redis.Conn type RedisSessionStore struct { - c redis.Conn + p *redis.Pool sid string lock sync.RWMutex values map[interface{}]interface{} @@ -60,13 +60,15 @@ func (rs *RedisSessionStore) SessionID() string { } func (rs *RedisSessionStore) SessionRelease(w http.ResponseWriter) { - defer rs.c.Close() + c := rs.p.Get() + defer c.Close() + b, err := encodeGob(rs.values) if err != nil { return } - rs.c.Do("SET", rs.sid, string(b)) - rs.c.Do("EXPIRE", rs.sid, rs.maxlifetime) + + c.Do("SET", rs.sid, string(b), "EX", rs.maxlifetime) } type RedisProvider struct { @@ -116,10 +118,8 @@ func (rp *RedisProvider) SessionInit(maxlifetime int64, savePath string) error { func (rp *RedisProvider) SessionRead(sid string) (SessionStore, error) { c := rp.poollist.Get() - if existed, err := redis.Int(c.Do("EXISTS", sid)); err != nil || existed == 0 { - c.Do("SET", sid) - } - c.Do("EXPIRE", sid, rp.maxlifetime) + defer c.Close() + kvs, err := redis.String(c.Do("GET", sid)) var kv map[interface{}]interface{} if len(kvs) == 0 { @@ -130,13 +130,15 @@ func (rp *RedisProvider) SessionRead(sid string) (SessionStore, error) { return nil, err } } - rs := &RedisSessionStore{c: c, sid: sid, values: kv, maxlifetime: rp.maxlifetime} + + rs := &RedisSessionStore{p: rp.poollist, sid: sid, values: kv, maxlifetime: rp.maxlifetime} return rs, nil } func (rp *RedisProvider) SessionExist(sid string) bool { c := rp.poollist.Get() defer c.Close() + if existed, err := redis.Int(c.Do("EXISTS", sid)); err != nil || existed == 0 { return false } else { @@ -146,11 +148,11 @@ func (rp *RedisProvider) SessionExist(sid string) bool { func (rp *RedisProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) { c := rp.poollist.Get() - if existed, err := redis.Int(c.Do("EXISTS", oldsid)); err != nil || existed == 0 { - c.Do("SET", oldsid) - } + defer c.Close() + c.Do("RENAME", oldsid, sid) c.Do("EXPIRE", sid, rp.maxlifetime) + kvs, err := redis.String(c.Do("GET", sid)) var kv map[interface{}]interface{} if len(kvs) == 0 { @@ -161,13 +163,15 @@ func (rp *RedisProvider) SessionRegenerate(oldsid, sid string) (SessionStore, er return nil, err } } - rs := &RedisSessionStore{c: c, sid: sid, values: kv, maxlifetime: rp.maxlifetime} + + rs := &RedisSessionStore{p: rp.poollist, sid: sid, values: kv, maxlifetime: rp.maxlifetime} return rs, nil } func (rp *RedisProvider) SessionDestroy(sid string) error { c := rp.poollist.Get() defer c.Close() + c.Do("DEL", sid) return nil } @@ -178,7 +182,6 @@ func (rp *RedisProvider) SessionGC() { //@todo func (rp *RedisProvider) SessionAll() int { - return 0 } From 190039b6f8a20bad7300aee6c0bc479b424d26a2 Mon Sep 17 00:00:00 2001 From: Kyle McCullough Date: Tue, 21 Jan 2014 23:58:57 -0600 Subject: [PATCH 03/11] Add a ReadOrCreate method: m := &User{Name: "Kyle"} // Returns a boolean indicating whether the object was created, // the primary key of the object, or an error. created, id, err := orm.ReadOrCreate(m, "Name") --- orm/orm.go | 14 ++++++++++++++ orm/orm_test.go | 38 ++++++++++++++++++++++++++++++++++++++ orm/types.go | 1 + 3 files changed, 53 insertions(+) diff --git a/orm/orm.go b/orm/orm.go index 71b4daa4..00439399 100644 --- a/orm/orm.go +++ b/orm/orm.go @@ -74,6 +74,20 @@ func (o *orm) Read(md interface{}, cols ...string) error { return nil } +// Try to read a row from the database, or insert one if it doesn't exist +func (o *orm) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) { + cols = append([]string{col1}, cols...) + mi, ind := o.getMiInd(md, true) + err := o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols) + if err == ErrNoRows { + // Create + id, err := o.Insert(md) + return (err == nil), id, err + } + + return false, ind.Field(mi.fields.pk.fieldIndex).Int(), err +} + // insert model data to database func (o *orm) Insert(md interface{}) (int64, error) { mi, ind := o.getMiInd(md, true) diff --git a/orm/orm_test.go b/orm/orm_test.go index f5101811..c951d5ca 100644 --- a/orm/orm_test.go +++ b/orm/orm_test.go @@ -1642,3 +1642,41 @@ func TestTransaction(t *testing.T) { throwFail(t, AssertIs(num, 1)) } + +func TestReadOrCreate(t *testing.T) { + u := &User{ + UserName: "Kyle", + Email: "kylemcc@gmail.com", + Password: "other_pass", + Status: 7, + IsStaff: false, + IsActive: true, + } + + created, pk, err := dORM.ReadOrCreate(u, "UserName") + throwFail(t, err) + throwFail(t, AssertIs(created, true)) + throwFail(t, AssertIs(u.UserName, "Kyle")) + throwFail(t, AssertIs(u.Email, "kylemcc@gmail.com")) + throwFail(t, AssertIs(u.Password, "other_pass")) + throwFail(t, AssertIs(u.Status, 7)) + throwFail(t, AssertIs(u.IsStaff, false)) + throwFail(t, AssertIs(u.IsActive, true)) + throwFail(t, AssertIs(u.Created.In(DefaultTimeLoc), u.Created.In(DefaultTimeLoc), test_Date)) + throwFail(t, AssertIs(u.Updated.In(DefaultTimeLoc), u.Updated.In(DefaultTimeLoc), test_DateTime)) + + nu := &User{UserName: u.UserName, Email: "someotheremail@gmail.com"} + created, pk, err = dORM.ReadOrCreate(nu, "UserName") + throwFail(t, err) + throwFail(t, AssertIs(created, false)) + throwFail(t, AssertIs(nu.Id, u.Id)) + throwFail(t, AssertIs(pk, u.Id)) + throwFail(t, AssertIs(nu.UserName, u.UserName)) + throwFail(t, AssertIs(nu.Email, u.Email)) // should contain the value in the table, not the one specified above + throwFail(t, AssertIs(nu.Password, u.Password)) + throwFail(t, AssertIs(nu.Status, u.Status)) + throwFail(t, AssertIs(nu.IsStaff, u.IsStaff)) + throwFail(t, AssertIs(nu.IsActive, u.IsActive)) + + dORM.Delete(u) +} diff --git a/orm/types.go b/orm/types.go index 6f13ed67..76e53017 100644 --- a/orm/types.go +++ b/orm/types.go @@ -23,6 +23,7 @@ type Fielder interface { // orm struct type Ormer interface { Read(interface{}, ...string) error + ReadOrCreate(interface{}, string, ...string) (bool, int64, error) Insert(interface{}) (int64, error) InsertMulti(int, interface{}) (int64, error) Update(interface{}, ...string) (int64, error) From d014ccfb8eb45ec2da30f83c6d525d27e7963e22 Mon Sep 17 00:00:00 2001 From: Pengfei Xue Date: Thu, 23 Jan 2014 19:28:58 +0800 Subject: [PATCH 04/11] bug fix, session stored in redis cannot be deleted --- controller.go | 1 + session/sess_redis.go | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/controller.go b/controller.go index 034e4cb3..83aaacfc 100644 --- a/controller.go +++ b/controller.go @@ -398,6 +398,7 @@ func (c *Controller) SessionRegenerateID() { // DestroySession cleans session data and session cookie. func (c *Controller) DestroySession() { + c.Ctx.Input.CruSession.Flush() GlobalSessions.SessionDestroy(c.Ctx.ResponseWriter, c.Ctx.Request) } diff --git a/session/sess_redis.go b/session/sess_redis.go index d19f316f..0326c6c2 100644 --- a/session/sess_redis.go +++ b/session/sess_redis.go @@ -60,6 +60,11 @@ func (rs *RedisSessionStore) SessionID() string { } func (rs *RedisSessionStore) SessionRelease(w http.ResponseWriter) { + // if rs.values is empty, return directly + if len(rs.values) < 1 { + return + } + c := rs.p.Get() defer c.Close() From 34eff4cc1fd084c100266ed83856c2aa9731857c Mon Sep 17 00:00:00 2001 From: Pengfei Xue Date: Sat, 25 Jan 2014 10:55:49 +0800 Subject: [PATCH 05/11] bugfix, delete the sid if it's values is empty * regenerate sid, if the old key doesn't exists, set the new one directly --- session/sess_redis.go | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/session/sess_redis.go b/session/sess_redis.go index 0326c6c2..b05e4831 100644 --- a/session/sess_redis.go +++ b/session/sess_redis.go @@ -60,14 +60,15 @@ func (rs *RedisSessionStore) SessionID() string { } func (rs *RedisSessionStore) SessionRelease(w http.ResponseWriter) { - // if rs.values is empty, return directly - if len(rs.values) < 1 { - return - } - c := rs.p.Get() defer c.Close() + // if rs.values is empty, return directly + if len(rs.values) < 1 { + c.Do("DEL", rs.sid) + return + } + b, err := encodeGob(rs.values) if err != nil { return @@ -155,8 +156,15 @@ func (rp *RedisProvider) SessionRegenerate(oldsid, sid string) (SessionStore, er c := rp.poollist.Get() defer c.Close() - c.Do("RENAME", oldsid, sid) - c.Do("EXPIRE", sid, rp.maxlifetime) + if existed, _ := redis.Int(c.Do("EXISTS", oldsid)); existed == 0 { + // oldsid doesn't exists, set the new sid directly + // ignore error here, since if it return error + // the existed value will be 0 + c.Do("SET", sid, "", "EX", rp.maxlifetime) + } else { + c.Do("RENAME", oldsid, sid) + c.Do("EXPIRE", sid, rp.maxlifetime) + } kvs, err := redis.String(c.Do("GET", sid)) var kv map[interface{}]interface{} From 9384e870832b9f130c5caaea1d14c0b53f4b66b1 Mon Sep 17 00:00:00 2001 From: slene Date: Mon, 27 Jan 2014 01:48:00 +0800 Subject: [PATCH 06/11] orm 1. add api: NewOrmWithDB, AddAliasWthDB; 2. RawSeter -> add api: RowsToMap, RowsToStruct; 3. RawSeter -> change api: Values, ValuesList, ValuesFlat add optional params comumns. --- orm/db.go | 4 + orm/db_alias.go | 126 +++++++++++++++++------------ orm/orm.go | 33 ++++++++ orm/orm_queryset.go | 30 +++++++ orm/orm_raw.go | 191 +++++++++++++++++++++++++++++++++++++++++--- orm/types.go | 20 ++++- 6 files changed, 338 insertions(+), 66 deletions(-) diff --git a/orm/db.go b/orm/db.go index f12e76fb..60e53765 100644 --- a/orm/db.go +++ b/orm/db.go @@ -1350,6 +1350,10 @@ func (d *dbBase) ReadValues(q dbQuerier, qs *querySet, mi *modelInfo, cond *Cond return cnt, nil } +func (d *dbBase) RowsTo(dbQuerier, *querySet, *modelInfo, *Condition, interface{}, string, string, *time.Location) (int64, error) { + return 0, nil +} + // flag of update joined record. func (d *dbBase) SupportUpdateJoin() bool { return true diff --git a/orm/db_alias.go b/orm/db_alias.go index d50b6ebd..22066514 100644 --- a/orm/db_alias.go +++ b/orm/db_alias.go @@ -3,7 +3,6 @@ package orm import ( "database/sql" "fmt" - "os" "reflect" "sync" "time" @@ -13,11 +12,11 @@ import ( type DriverType int const ( - _ DriverType = iota // int enum type - DR_MySQL // mysql - DR_Sqlite // sqlite - DR_Oracle // oracle - DR_Postgres // pgsql + _ DriverType = iota // int enum type + DR_MySQL // mysql + DR_Sqlite // sqlite + DR_Oracle // oracle + DR_Postgres // pgsql ) // database driver string. @@ -96,40 +95,15 @@ type alias struct { Engine string } -// Setting the database connect params. Use the database driver self dataSource args. -func RegisterDataBase(aliasName, driverName, dataSource string, params ...int) { - al := new(alias) - al.Name = aliasName - al.DriverName = driverName - al.DataSource = dataSource - - var ( - err error - ) - - if dr, ok := drivers[driverName]; ok { - al.DbBaser = dbBasers[dr] - al.Driver = dr - } else { - err = fmt.Errorf("driver name `%s` have not registered", driverName) - goto end - } - - if dataBaseCache.add(aliasName, al) == false { - err = fmt.Errorf("db name `%s` already registered, cannot reuse", aliasName) - goto end - } - - al.DB, err = sql.Open(driverName, dataSource) - if err != nil { - err = fmt.Errorf("register db `%s`, %s", aliasName, err.Error()) - goto end - } - +func detectTZ(al *alias) { // orm timezone system match database // default use Local al.TZ = time.Local + if al.DriverName == "sphinx" { + return + } + switch al.Driver { case DR_MySQL: row := al.DB.QueryRow("SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP)") @@ -173,6 +147,60 @@ func RegisterDataBase(aliasName, driverName, dataSource string, params ...int) { DebugLog.Printf("Detect DB timezone: %s %s\n", tz, err.Error()) } } +} + +func addAliasWthDB(aliasName, driverName string, db *sql.DB) (*alias, error) { + al := new(alias) + al.Name = aliasName + al.DriverName = driverName + al.DB = db + + if dr, ok := drivers[driverName]; ok { + al.DbBaser = dbBasers[dr] + al.Driver = dr + } else { + return nil, fmt.Errorf("driver name `%s` have not registered", driverName) + } + + err := db.Ping() + if err != nil { + return nil, fmt.Errorf("register db Ping `%s`, %s", aliasName, err.Error()) + } + + if dataBaseCache.add(aliasName, al) == false { + return nil, fmt.Errorf("db name `%s` already registered, cannot reuse", aliasName) + } + + return al, nil +} + +func AddAliasWthDB(aliasName, driverName string, db *sql.DB) error { + _, err := addAliasWthDB(aliasName, driverName, db) + return err +} + +// Setting the database connect params. Use the database driver self dataSource args. +func RegisterDataBase(aliasName, driverName, dataSource string, params ...int) error { + var ( + err error + db *sql.DB + al *alias + ) + + db, err = sql.Open(driverName, dataSource) + if err != nil { + err = fmt.Errorf("register db `%s`, %s", aliasName, err.Error()) + goto end + } + + al, err = addAliasWthDB(aliasName, driverName, db) + if err != nil { + goto end + } + + al.DataSource = dataSource + + detectTZ(al) for i, v := range params { switch i { @@ -183,39 +211,37 @@ func RegisterDataBase(aliasName, driverName, dataSource string, params ...int) { } } - err = al.DB.Ping() - if err != nil { - err = fmt.Errorf("register db `%s`, %s", aliasName, err.Error()) - goto end - } - end: if err != nil { - fmt.Println(err.Error()) - os.Exit(2) + if db != nil { + db.Close() + } + DebugLog.Println(err.Error()) } + + return err } // Register a database driver use specify driver name, this can be definition the driver is which database type. -func RegisterDriver(driverName string, typ DriverType) { +func RegisterDriver(driverName string, typ DriverType) error { if t, ok := drivers[driverName]; ok == false { drivers[driverName] = typ } else { if t != typ { - fmt.Sprintf("driverName `%s` db driver already registered and is other type\n", driverName) - os.Exit(2) + return fmt.Errorf("driverName `%s` db driver already registered and is other type\n", driverName) } } + return nil } // Change the database default used timezone -func SetDataBaseTZ(aliasName string, tz *time.Location) { +func SetDataBaseTZ(aliasName string, tz *time.Location) error { if al, ok := dataBaseCache.get(aliasName); ok { al.TZ = tz } else { - fmt.Sprintf("DataBase name `%s` not registered\n", aliasName) - os.Exit(2) + return fmt.Errorf("DataBase name `%s` not registered\n", aliasName) } + return nil } // Change the max idle conns for *sql.DB, use specify database alias name diff --git a/orm/orm.go b/orm/orm.go index 00439399..25857fa8 100644 --- a/orm/orm.go +++ b/orm/orm.go @@ -439,6 +439,12 @@ func (o *orm) Driver() Driver { return driver(o.alias.Name) } +func (o *orm) GetDB() dbQuerier { + panic(ErrNotImplement) + // not enough + return o.db +} + // create new orm func NewOrm() Ormer { BootStrap() // execute only once @@ -450,3 +456,30 @@ func NewOrm() Ormer { } return o } + +// create a new ormer object with specify *sql.DB for query +func NewOrmWithDB(driverName, aliasName string, db *sql.DB) (Ormer, error) { + var al *alias + + if dr, ok := drivers[driverName]; ok { + al = new(alias) + al.DbBaser = dbBasers[dr] + al.Driver = dr + } else { + return nil, fmt.Errorf("driver name `%s` have not registered", driverName) + } + + al.Name = aliasName + al.DriverName = driverName + + o := new(orm) + o.alias = al + + if Debug { + o.db = newDbQueryLog(o.alias, db) + } else { + o.db = db + } + + return o, nil +} diff --git a/orm/orm_queryset.go b/orm/orm_queryset.go index ad8a9374..d16f8eb5 100644 --- a/orm/orm_queryset.go +++ b/orm/orm_queryset.go @@ -197,6 +197,36 @@ func (o *querySet) ValuesFlat(result *ParamsList, expr string) (int64, error) { return o.orm.alias.DbBaser.ReadValues(o.orm.db, o, o.mi, o.cond, []string{expr}, result, o.orm.alias.TZ) } +// query all rows into map[string]interface with specify key and value column name. +// keyCol = "name", valueCol = "value" +// table data +// name | value +// total | 100 +// found | 200 +// to map[string]interface{}{ +// "total": 100, +// "found": 200, +// } +func (o *querySet) RowsToMap(result *Params, keyCol, valueCol string) (int64, error) { + panic(ErrNotImplement) + return o.orm.alias.DbBaser.RowsTo(o.orm.db, o, o.mi, o.cond, result, keyCol, valueCol, o.orm.alias.TZ) +} + +// query all rows into struct with specify key and value column name. +// keyCol = "name", valueCol = "value" +// table data +// name | value +// total | 100 +// found | 200 +// to struct { +// Total int +// Found int +// } +func (o *querySet) RowsToStruct(ptrStruct interface{}, keyCol, valueCol string) (int64, error) { + panic(ErrNotImplement) + return o.orm.alias.DbBaser.RowsTo(o.orm.db, o, o.mi, o.cond, ptrStruct, keyCol, valueCol, o.orm.alias.TZ) +} + // create new QuerySeter. func newQuerySet(orm *orm, mi *modelInfo) QuerySeter { o := new(querySet) diff --git a/orm/orm_raw.go b/orm/orm_raw.go index 3f5fb162..a968e347 100644 --- a/orm/orm_raw.go +++ b/orm/orm_raw.go @@ -518,7 +518,7 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) { return cnt, nil } -func (o *rawSet) readValues(container interface{}) (int64, error) { +func (o *rawSet) readValues(container interface{}, needCols []string) (int64, error) { var ( maps []Params lists []ParamsList @@ -552,20 +552,38 @@ func (o *rawSet) readValues(container interface{}) (int64, error) { defer rs.Close() var ( - refs []interface{} - cnt int64 - cols []string + refs []interface{} + cnt int64 + cols []string + indexs []int ) + for rs.Next() { if cnt == 0 { if columns, err := rs.Columns(); err != nil { return 0, err } else { + if len(needCols) > 0 { + indexs = make([]int, 0, len(needCols)) + } else { + indexs = make([]int, 0, len(columns)) + } + cols = columns refs = make([]interface{}, len(cols)) for i, _ := range refs { var ref sql.NullString refs[i] = &ref + + if len(needCols) > 0 { + for _, c := range needCols { + if c == cols[i] { + indexs = append(indexs, i) + } + } + } else { + indexs = append(indexs, i) + } } } } @@ -577,7 +595,8 @@ func (o *rawSet) readValues(container interface{}) (int64, error) { switch typ { case 1: params := make(Params, len(cols)) - for i, ref := range refs { + for _, i := range indexs { + ref := refs[i] value := reflect.Indirect(reflect.ValueOf(ref)).Interface().(sql.NullString) if value.Valid { params[cols[i]] = value.String @@ -588,7 +607,8 @@ func (o *rawSet) readValues(container interface{}) (int64, error) { maps = append(maps, params) case 2: params := make(ParamsList, 0, len(cols)) - for _, ref := range refs { + for _, i := range indexs { + ref := refs[i] value := reflect.Indirect(reflect.ValueOf(ref)).Interface().(sql.NullString) if value.Valid { params = append(params, value.String) @@ -598,7 +618,8 @@ func (o *rawSet) readValues(container interface{}) (int64, error) { } lists = append(lists, params) case 3: - for _, ref := range refs { + for _, i := range indexs { + ref := refs[i] value := reflect.Indirect(reflect.ValueOf(ref)).Interface().(sql.NullString) if value.Valid { list = append(list, value.String) @@ -623,19 +644,163 @@ func (o *rawSet) readValues(container interface{}) (int64, error) { return cnt, nil } +func (o *rawSet) queryRowsTo(container interface{}, keyCol, valueCol string) (int64, error) { + var ( + maps Params + ind *reflect.Value + ) + + typ := 0 + switch container.(type) { + case *Params: + typ = 1 + default: + typ = 2 + vl := reflect.ValueOf(container) + id := reflect.Indirect(vl) + if vl.Kind() != reflect.Ptr || id.Kind() != reflect.Struct { + panic(fmt.Errorf(" RowsTo unsupport type `%T` need ptr struct", container)) + } + + ind = &id + } + + query := o.query + o.orm.alias.DbBaser.ReplaceMarks(&query) + + args := getFlatParams(nil, o.args, o.orm.alias.TZ) + + var rs *sql.Rows + if r, err := o.orm.db.Query(query, args...); err != nil { + return 0, err + } else { + rs = r + } + + defer rs.Close() + + var ( + refs []interface{} + cnt int64 + cols []string + ) + + var ( + keyIndex = -1 + valueIndex = -1 + ) + + for rs.Next() { + if cnt == 0 { + if columns, err := rs.Columns(); err != nil { + return 0, err + } else { + cols = columns + refs = make([]interface{}, len(cols)) + for i, _ := range refs { + if keyCol == cols[i] { + keyIndex = i + } + + if typ == 1 || keyIndex == i { + var ref sql.NullString + refs[i] = &ref + } else { + var ref interface{} + refs[i] = &ref + } + + if valueCol == cols[i] { + valueIndex = i + } + } + + if keyIndex == -1 || valueIndex == -1 { + panic(fmt.Errorf(" RowsTo unknown key, value column name `%s: %s`", keyCol, valueCol)) + } + } + } + + if err := rs.Scan(refs...); err != nil { + return 0, err + } + + if cnt == 0 { + switch typ { + case 1: + maps = make(Params) + } + } + + key := reflect.Indirect(reflect.ValueOf(refs[keyIndex])).Interface().(sql.NullString).String + + switch typ { + case 1: + value := reflect.Indirect(reflect.ValueOf(refs[valueIndex])).Interface().(sql.NullString) + if value.Valid { + maps[key] = value.String + } else { + maps[key] = nil + } + + default: + if id := ind.FieldByName(camelString(key)); id.IsValid() { + o.setFieldValue(id, reflect.ValueOf(refs[valueIndex]).Elem().Interface()) + } + } + + cnt++ + } + + if typ == 1 { + v, _ := container.(*Params) + *v = maps + } + + return cnt, nil +} + // query data to []map[string]interface -func (o *rawSet) Values(container *[]Params) (int64, error) { - return o.readValues(container) +func (o *rawSet) Values(container *[]Params, cols ...string) (int64, error) { + return o.readValues(container, cols) } // query data to [][]interface -func (o *rawSet) ValuesList(container *[]ParamsList) (int64, error) { - return o.readValues(container) +func (o *rawSet) ValuesList(container *[]ParamsList, cols ...string) (int64, error) { + return o.readValues(container, cols) } // query data to []interface -func (o *rawSet) ValuesFlat(container *ParamsList) (int64, error) { - return o.readValues(container) +func (o *rawSet) ValuesFlat(container *ParamsList, cols ...string) (int64, error) { + return o.readValues(container, cols) +} + +// query all rows into map[string]interface with specify key and value column name. +// keyCol = "name", valueCol = "value" +// table data +// name | value +// total | 100 +// found | 200 +// to map[string]interface{}{ +// "total": 100, +// "found": 200, +// } +func (o *rawSet) RowsToMap(result *Params, keyCol, valueCol string) (int64, error) { + return o.queryRowsTo(result, keyCol, valueCol) +} + +// query all rows into struct with specify key and value column name. +// keyCol = "name", valueCol = "value" +// table data +// name | value +// total | 100 +// found | 200 +// to struct { +// Total int +// Found int +// } +func (o *rawSet) RowsToStruct(ptrStruct interface{}, keyCol, valueCol string) (int64, error) { + return o.queryRowsTo(ptrStruct, keyCol, valueCol) } // return prepared raw statement for used in times. diff --git a/orm/types.go b/orm/types.go index 76e53017..4361c62c 100644 --- a/orm/types.go +++ b/orm/types.go @@ -37,6 +37,7 @@ type Ormer interface { Rollback() error Raw(string, ...interface{}) RawSeter Driver() Driver + GetDB() dbQuerier } // insert prepared statement @@ -64,6 +65,8 @@ type QuerySeter interface { Values(*[]Params, ...string) (int64, error) ValuesList(*[]ParamsList, ...string) (int64, error) ValuesFlat(*ParamsList, string) (int64, error) + RowsToMap(*Params, string, string) (int64, error) + RowsToStruct(interface{}, string, string) (int64, error) } // model to model query struct @@ -87,9 +90,11 @@ type RawSeter interface { QueryRow(...interface{}) error QueryRows(...interface{}) (int64, error) SetArgs(...interface{}) RawSeter - Values(*[]Params) (int64, error) - ValuesList(*[]ParamsList) (int64, error) - ValuesFlat(*ParamsList) (int64, error) + Values(*[]Params, ...string) (int64, error) + ValuesList(*[]ParamsList, ...string) (int64, error) + ValuesFlat(*ParamsList, ...string) (int64, error) + RowsToMap(*Params, string, string) (int64, error) + RowsToStruct(interface{}, string, string) (int64, error) Prepare() (RawPreparer, error) } @@ -109,6 +114,14 @@ type dbQuerier interface { QueryRow(query string, args ...interface{}) *sql.Row } +// type DB interface { +// Begin() (*sql.Tx, error) +// Prepare(query string) (stmtQuerier, error) +// Exec(query string, args ...interface{}) (sql.Result, error) +// Query(query string, args ...interface{}) (*sql.Rows, error) +// QueryRow(query string, args ...interface{}) *sql.Row +// } + // transaction beginner type txer interface { Begin() (*sql.Tx, error) @@ -139,6 +152,7 @@ type dbBaser interface { GenerateOperatorLeftCol(*fieldInfo, string, *string) PrepareInsert(dbQuerier, *modelInfo) (stmtQuerier, string, error) ReadValues(dbQuerier, *querySet, *modelInfo, *Condition, []string, interface{}, *time.Location) (int64, error) + RowsTo(dbQuerier, *querySet, *modelInfo, *Condition, interface{}, string, string, *time.Location) (int64, error) MaxLimit() uint64 TableQuote() string ReplaceMarks(*string) From d93f1120835684a6ac07001b63ce3c2c5398f730 Mon Sep 17 00:00:00 2001 From: cloudaice Date: Tue, 28 Jan 2014 11:26:43 +0800 Subject: [PATCH 07/11] fixed bug: in logs package check if platform is windows --- logs/console.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/logs/console.go b/logs/console.go index dd271ff1..438facfe 100644 --- a/logs/console.go +++ b/logs/console.go @@ -4,6 +4,7 @@ import ( "encoding/json" "log" "os" + "runtime" ) type Brush func(string) string @@ -54,7 +55,11 @@ func (c *ConsoleWriter) WriteMsg(msg string, level int) error { if level < c.Level { return nil } - c.lg.Println(colors[level](msg)) + if goos := runtime.GOOS; goos == "windows" { + c.lg.Println(msg) + } else { + c.lg.Println(colors[level](msg)) + } return nil } From 0e2872324f0542ef3d0ab2bcec96e1ee25ba96c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=82=85=E5=B0=8F=E9=BB=91?= Date: Wed, 29 Jan 2014 01:05:56 +0800 Subject: [PATCH 08/11] add comments for session packages, part 1 --- session/sess_cookie.go | 27 ++++++++++++++++++++++++++- session/sess_file.go | 22 ++++++++++++++++++++++ session/session.go | 35 +++++++++++++++++++++++++++-------- 3 files changed, 75 insertions(+), 9 deletions(-) diff --git a/session/sess_cookie.go b/session/sess_cookie.go index 7962be18..ddf1a902 100644 --- a/session/sess_cookie.go +++ b/session/sess_cookie.go @@ -11,12 +11,15 @@ import ( var cookiepder = &CookieProvider{} +// Cookie SessionStore type CookieSessionStore struct { sid string - values map[interface{}]interface{} //session data + values map[interface{}]interface{} // session data lock sync.RWMutex } +// Set value to cookie session. +// the value are encoded as gob with hash block string. func (st *CookieSessionStore) Set(key, value interface{}) error { st.lock.Lock() defer st.lock.Unlock() @@ -24,6 +27,7 @@ func (st *CookieSessionStore) Set(key, value interface{}) error { return nil } +// Get value from cookie session func (st *CookieSessionStore) Get(key interface{}) interface{} { st.lock.RLock() defer st.lock.RUnlock() @@ -35,6 +39,7 @@ func (st *CookieSessionStore) Get(key interface{}) interface{} { return nil } +// Delete value in cookie session func (st *CookieSessionStore) Delete(key interface{}) error { st.lock.Lock() defer st.lock.Unlock() @@ -42,6 +47,7 @@ func (st *CookieSessionStore) Delete(key interface{}) error { return nil } +// Clean all values in cookie session func (st *CookieSessionStore) Flush() error { st.lock.Lock() defer st.lock.Unlock() @@ -49,10 +55,12 @@ func (st *CookieSessionStore) Flush() error { return nil } +// Return id of this cookie session func (st *CookieSessionStore) SessionID() string { return st.sid } +// Write cookie session to http response cookie func (st *CookieSessionStore) SessionRelease(w http.ResponseWriter) { str, err := encodeCookie(cookiepder.block, cookiepder.config.SecurityKey, @@ -79,12 +87,21 @@ type cookieConfig struct { Maxage int `json:"maxage"` } +// Cookie session provider type CookieProvider struct { maxlifetime int64 config *cookieConfig block cipher.Block } +// Init cookie session provider with max lifetime and config json. +// maxlifetime is ignored. +// json config: +// securityKey - hash string +// blockKey - gob encode hash string. it's saved as aes crypto. +// securityName - recognized name in encoded cookie string +// cookieName - cookie name +// maxage - cookie max life time. func (pder *CookieProvider) SessionInit(maxlifetime int64, config string) error { pder.config = &cookieConfig{} err := json.Unmarshal([]byte(config), pder.config) @@ -104,6 +121,8 @@ func (pder *CookieProvider) SessionInit(maxlifetime int64, config string) error return nil } +// Get SessionStore in cooke. +// decode cooke string to map and put into SessionStore with sid. func (pder *CookieProvider) SessionRead(sid string) (SessionStore, error) { maps, _ := decodeCookie(pder.block, pder.config.SecurityKey, @@ -116,26 +135,32 @@ func (pder *CookieProvider) SessionRead(sid string) (SessionStore, error) { return rs, nil } +// Cookie session is always existed func (pder *CookieProvider) SessionExist(sid string) bool { return true } +// Implement method, no used. func (pder *CookieProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) { return nil, nil } +// Implement method, no used. func (pder *CookieProvider) SessionDestroy(sid string) error { return nil } +// Implement method, no used. func (pder *CookieProvider) SessionGC() { return } +// Implement method, return 0. func (pder *CookieProvider) SessionAll() int { return 0 } +// Implement method, no used. func (pder *CookieProvider) SessionUpdate(sid string) error { return nil } diff --git a/session/sess_file.go b/session/sess_file.go index 5d33d0e2..73eec874 100644 --- a/session/sess_file.go +++ b/session/sess_file.go @@ -18,6 +18,7 @@ var ( gcmaxlifetime int64 ) +// File session store type FileSessionStore struct { f *os.File sid string @@ -25,6 +26,7 @@ type FileSessionStore struct { values map[interface{}]interface{} } +// Set value to file session func (fs *FileSessionStore) Set(key, value interface{}) error { fs.lock.Lock() defer fs.lock.Unlock() @@ -32,6 +34,7 @@ func (fs *FileSessionStore) Set(key, value interface{}) error { return nil } +// Get value from file session func (fs *FileSessionStore) Get(key interface{}) interface{} { fs.lock.RLock() defer fs.lock.RUnlock() @@ -43,6 +46,7 @@ func (fs *FileSessionStore) Get(key interface{}) interface{} { return nil } +// Delete value in file session by given key func (fs *FileSessionStore) Delete(key interface{}) error { fs.lock.Lock() defer fs.lock.Unlock() @@ -50,6 +54,7 @@ func (fs *FileSessionStore) Delete(key interface{}) error { return nil } +// Clean all values in file session func (fs *FileSessionStore) Flush() error { fs.lock.Lock() defer fs.lock.Unlock() @@ -57,10 +62,12 @@ func (fs *FileSessionStore) Flush() error { return nil } +// Get file session store id func (fs *FileSessionStore) SessionID() string { return fs.sid } +// Write file session to local file with Gob string func (fs *FileSessionStore) SessionRelease(w http.ResponseWriter) { defer fs.f.Close() b, err := encodeGob(fs.values) @@ -72,17 +79,23 @@ func (fs *FileSessionStore) SessionRelease(w http.ResponseWriter) { fs.f.Write(b) } +// File session provider type FileProvider struct { maxlifetime int64 savePath string } +// Init file session provider. +// savePath sets the session files path. func (fp *FileProvider) SessionInit(maxlifetime int64, savePath string) error { fp.maxlifetime = maxlifetime fp.savePath = savePath return nil } +// Read file session by sid. +// if file is not exist, create it. +// the file path is generated from sid string. func (fp *FileProvider) SessionRead(sid string) (SessionStore, error) { err := os.MkdirAll(path.Join(fp.savePath, string(sid[0]), string(sid[1])), 0777) if err != nil { @@ -117,6 +130,8 @@ func (fp *FileProvider) SessionRead(sid string) (SessionStore, error) { return ss, nil } +// Check file session exist. +// it checkes the file named from sid exist or not. func (fp *FileProvider) SessionExist(sid string) bool { _, err := os.Stat(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid)) if err == nil { @@ -126,16 +141,20 @@ func (fp *FileProvider) SessionExist(sid string) bool { } } +// Remove all files in this save path func (fp *FileProvider) SessionDestroy(sid string) error { os.Remove(path.Join(fp.savePath)) return nil } +// Recycle files in save path func (fp *FileProvider) SessionGC() { gcmaxlifetime = fp.maxlifetime filepath.Walk(fp.savePath, gcpath) } +// Get active file session number. +// it walks save path to count files. func (fp *FileProvider) SessionAll() int { a := &activeSession{} err := filepath.Walk(fp.savePath, func(path string, f os.FileInfo, err error) error { @@ -148,6 +167,8 @@ func (fp *FileProvider) SessionAll() int { return a.total } +// Generate new sid for file session. +// it delete old file and create new file named from new sid. func (fp *FileProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) { err := os.MkdirAll(path.Join(fp.savePath, string(oldsid[0]), string(oldsid[1])), 0777) if err != nil { @@ -197,6 +218,7 @@ func (fp *FileProvider) SessionRegenerate(oldsid, sid string) (SessionStore, err return ss, nil } +// remove file in save path if expired func gcpath(path string, info os.FileInfo, err error) error { if err != nil { return err diff --git a/session/session.go b/session/session.go index f41ba85b..d1a44538 100644 --- a/session/session.go +++ b/session/session.go @@ -14,6 +14,7 @@ import ( "time" ) +// SessionStore contains all data for one session process with specific id. type SessionStore interface { Set(key, value interface{}) error //set session value Get(key interface{}) interface{} //get session value @@ -23,6 +24,8 @@ type SessionStore interface { Flush() error //delete all data } +// Provider contains global session methods and saved SessionStores. +// it can operate a SessionStore by its id. type Provider interface { SessionInit(gclifetime int64, config string) error SessionRead(sid string) (SessionStore, error) @@ -61,16 +64,24 @@ type managerConfig struct { ProviderConfig string `json:"providerConfig"` } +// Manager contains Provider and its configuration. type Manager struct { provider Provider config *managerConfig } -//options -//1. is https default false -//2. hashfunc default sha1 -//3. hashkey default beegosessionkey -//4. maxage default is none +// Create new Manager with provider name and json config string. +// provider name: +// 1. cookie +// 2. file +// 3. memory +// 4. redis +// 5. mysql +// json config: +// 1. is https default false +// 2. hashfunc default sha1 +// 3. hashkey default beegosessionkey +// 4. maxage default is none func NewManager(provideName, config string) (*Manager, error) { provider, ok := provides[provideName] if !ok { @@ -102,7 +113,8 @@ func NewManager(provideName, config string) (*Manager, error) { }, nil } -//get Session +// Start session. generate or read the session id from http request. +// if session id exists, return SessionStore with this id. func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session SessionStore) { cookie, err := r.Cookie(manager.config.CookieName) if err != nil || cookie.Value == "" { @@ -144,7 +156,7 @@ func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (se return } -//Destroy sessionid +// Destroy session by its id in http request cookie. func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie(manager.config.CookieName) if err != nil || cookie.Value == "" { @@ -161,16 +173,20 @@ func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) { } } +// Get SessionStore by its id. func (manager *Manager) GetProvider(sid string) (sessions SessionStore, err error) { sessions, err = manager.provider.SessionRead(sid) return } +// Start session gc process. +// it can do gc in times after gc lifetime. func (manager *Manager) GC() { manager.provider.SessionGC() time.AfterFunc(time.Duration(manager.config.Gclifetime)*time.Second, func() { manager.GC() }) } +// Regenerate a session id for this SessionStore who's id is saving in http request. func (manager *Manager) SessionRegenerateId(w http.ResponseWriter, r *http.Request) (session SessionStore) { sid := manager.sessionId(r) cookie, err := r.Cookie(manager.config.CookieName) @@ -198,20 +214,23 @@ func (manager *Manager) SessionRegenerateId(w http.ResponseWriter, r *http.Reque return } +// Get all active sessions count number. func (manager *Manager) GetActiveSession() int { return manager.provider.SessionAll() } +// Set hash function for generating session id. func (manager *Manager) SetHashFunc(hasfunc, hashkey string) { manager.config.SessionIDHashFunc = hasfunc manager.config.SessionIDHashKey = hashkey } +// Set cookie with https. func (manager *Manager) SetSecure(secure bool) { manager.config.Secure = secure } -//remote_addr cruunixnano randdata +// generate session id with rand string, unix nano time, remote addr by hash function. func (manager *Manager) sessionId(r *http.Request) (sid string) { bs := make([]byte, 24) if _, err := io.ReadFull(rand.Reader, bs); err != nil { From 682544165fdd5fc849c8d1c9f6b7c11975cc59d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=82=85=E5=B0=8F=E9=BB=91?= Date: Wed, 29 Jan 2014 18:15:09 +0800 Subject: [PATCH 09/11] add comments for session packages, part 2 --- session/sess_mem.go | 22 +++++++++++++++++++--- session/sess_mysql.go | 32 ++++++++++++++++++++++++++------ session/sess_redis.go | 21 ++++++++++++++++++--- 3 files changed, 63 insertions(+), 12 deletions(-) diff --git a/session/sess_mem.go b/session/sess_mem.go index c74c2602..33c84717 100644 --- a/session/sess_mem.go +++ b/session/sess_mem.go @@ -9,6 +9,8 @@ import ( var mempder = &MemProvider{list: list.New(), sessions: make(map[string]*list.Element)} +// memory session store. +// it saved sessions in a map in memory. type MemSessionStore struct { sid string //session id timeAccessed time.Time //last access time @@ -16,6 +18,7 @@ type MemSessionStore struct { lock sync.RWMutex } +// set value to memory session func (st *MemSessionStore) Set(key, value interface{}) error { st.lock.Lock() defer st.lock.Unlock() @@ -23,6 +26,7 @@ func (st *MemSessionStore) Set(key, value interface{}) error { return nil } +// get value from memory session by key func (st *MemSessionStore) Get(key interface{}) interface{} { st.lock.RLock() defer st.lock.RUnlock() @@ -34,6 +38,7 @@ func (st *MemSessionStore) Get(key interface{}) interface{} { return nil } +// delete in memory session by key func (st *MemSessionStore) Delete(key interface{}) error { st.lock.Lock() defer st.lock.Unlock() @@ -41,6 +46,7 @@ func (st *MemSessionStore) Delete(key interface{}) error { return nil } +// clear all values in memory session func (st *MemSessionStore) Flush() error { st.lock.Lock() defer st.lock.Unlock() @@ -48,27 +54,31 @@ func (st *MemSessionStore) Flush() error { return nil } +// get this id of memory session store func (st *MemSessionStore) SessionID() string { return st.sid } +// Implement method, no used. func (st *MemSessionStore) SessionRelease(w http.ResponseWriter) { } type MemProvider struct { - lock sync.RWMutex //用来锁 - sessions map[string]*list.Element //用来存储在内存 - list *list.List //用来做gc + lock sync.RWMutex // locker + sessions map[string]*list.Element // map in memory + list *list.List // for gc maxlifetime int64 savePath string } +// init memory session func (pder *MemProvider) SessionInit(maxlifetime int64, savePath string) error { pder.maxlifetime = maxlifetime pder.savePath = savePath return nil } +// get memory session store by sid func (pder *MemProvider) SessionRead(sid string) (SessionStore, error) { pder.lock.RLock() if element, ok := pder.sessions[sid]; ok { @@ -87,6 +97,7 @@ func (pder *MemProvider) SessionRead(sid string) (SessionStore, error) { return nil, nil } +// check session store exist in memory session by sid func (pder *MemProvider) SessionExist(sid string) bool { pder.lock.RLock() defer pder.lock.RUnlock() @@ -97,6 +108,7 @@ func (pder *MemProvider) SessionExist(sid string) bool { } } +// generate new sid for session store in memory session func (pder *MemProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) { pder.lock.RLock() if element, ok := pder.sessions[oldsid]; ok { @@ -120,6 +132,7 @@ func (pder *MemProvider) SessionRegenerate(oldsid, sid string) (SessionStore, er return nil, nil } +// delete session store in memory session by id func (pder *MemProvider) SessionDestroy(sid string) error { pder.lock.Lock() defer pder.lock.Unlock() @@ -131,6 +144,7 @@ func (pder *MemProvider) SessionDestroy(sid string) error { return nil } +// clean expired session stores in memory session func (pder *MemProvider) SessionGC() { pder.lock.RLock() for { @@ -152,10 +166,12 @@ func (pder *MemProvider) SessionGC() { pder.lock.RUnlock() } +// get count number of memory session func (pder *MemProvider) SessionAll() int { return pder.list.Len() } +// expand time of session store by id in memory session func (pder *MemProvider) SessionUpdate(sid string) error { pder.lock.Lock() defer pder.lock.Unlock() diff --git a/session/sess_mysql.go b/session/sess_mysql.go index 7bad9e4a..b471c6c0 100644 --- a/session/sess_mysql.go +++ b/session/sess_mysql.go @@ -1,11 +1,12 @@ package session -//CREATE TABLE `session` ( -// `session_key` char(64) NOT NULL, -// `session_data` blob, -// `session_expiry` int(11) unsigned NOT NULL, -// PRIMARY KEY (`session_key`) -//) ENGINE=MyISAM DEFAULT CHARSET=utf8; +// mysql session support need create table as sql: +// CREATE TABLE `session` ( +// `session_key` char(64) NOT NULL, +// session_data` blob, +// `session_expiry` int(11) unsigned NOT NULL, +// PRIMARY KEY (`session_key`) +// ) ENGINE=MyISAM DEFAULT CHARSET=utf8; import ( "database/sql" @@ -18,6 +19,7 @@ import ( var mysqlpder = &MysqlProvider{} +// mysql session store type MysqlSessionStore struct { c *sql.DB sid string @@ -25,6 +27,8 @@ type MysqlSessionStore struct { values map[interface{}]interface{} } +// set value in mysql session. +// it is temp value in map. func (st *MysqlSessionStore) Set(key, value interface{}) error { st.lock.Lock() defer st.lock.Unlock() @@ -32,6 +36,7 @@ func (st *MysqlSessionStore) Set(key, value interface{}) error { return nil } +// get value from mysql session func (st *MysqlSessionStore) Get(key interface{}) interface{} { st.lock.RLock() defer st.lock.RUnlock() @@ -43,6 +48,7 @@ func (st *MysqlSessionStore) Get(key interface{}) interface{} { return nil } +// delete value in mysql session func (st *MysqlSessionStore) Delete(key interface{}) error { st.lock.Lock() defer st.lock.Unlock() @@ -50,6 +56,7 @@ func (st *MysqlSessionStore) Delete(key interface{}) error { return nil } +// clear all values in mysql session func (st *MysqlSessionStore) Flush() error { st.lock.Lock() defer st.lock.Unlock() @@ -57,10 +64,13 @@ func (st *MysqlSessionStore) Flush() error { return nil } +// get session id of this mysql session store func (st *MysqlSessionStore) SessionID() string { return st.sid } +// save mysql session values to database. +// must call this method to save values to database. func (st *MysqlSessionStore) SessionRelease(w http.ResponseWriter) { defer st.c.Close() b, err := encodeGob(st.values) @@ -72,11 +82,13 @@ func (st *MysqlSessionStore) SessionRelease(w http.ResponseWriter) { } +// mysql session provider type MysqlProvider struct { maxlifetime int64 savePath string } +// connect to mysql func (mp *MysqlProvider) connectInit() *sql.DB { db, e := sql.Open("mysql", mp.savePath) if e != nil { @@ -85,12 +97,15 @@ func (mp *MysqlProvider) connectInit() *sql.DB { return db } +// init mysql session. +// savepath is the connection string of mysql. func (mp *MysqlProvider) SessionInit(maxlifetime int64, savePath string) error { mp.maxlifetime = maxlifetime mp.savePath = savePath return nil } +// get mysql session by sid func (mp *MysqlProvider) SessionRead(sid string) (SessionStore, error) { c := mp.connectInit() row := c.QueryRow("select session_data from session where session_key=?", sid) @@ -113,6 +128,7 @@ func (mp *MysqlProvider) SessionRead(sid string) (SessionStore, error) { return rs, nil } +// check mysql session exist func (mp *MysqlProvider) SessionExist(sid string) bool { c := mp.connectInit() defer c.Close() @@ -126,6 +142,7 @@ func (mp *MysqlProvider) SessionExist(sid string) bool { } } +// generate new sid for mysql session func (mp *MysqlProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) { c := mp.connectInit() row := c.QueryRow("select session_data from session where session_key=?", oldsid) @@ -148,6 +165,7 @@ func (mp *MysqlProvider) SessionRegenerate(oldsid, sid string) (SessionStore, er return rs, nil } +// delete mysql session by sid func (mp *MysqlProvider) SessionDestroy(sid string) error { c := mp.connectInit() c.Exec("DELETE FROM session where session_key=?", sid) @@ -155,6 +173,7 @@ func (mp *MysqlProvider) SessionDestroy(sid string) error { return nil } +// delete expired values in mysql session func (mp *MysqlProvider) SessionGC() { c := mp.connectInit() c.Exec("DELETE from session where session_expiry < ?", time.Now().Unix()-mp.maxlifetime) @@ -162,6 +181,7 @@ func (mp *MysqlProvider) SessionGC() { return } +// count values in mysql session func (mp *MysqlProvider) SessionAll() int { c := mp.connectInit() defer c.Close() diff --git a/session/sess_redis.go b/session/sess_redis.go index b05e4831..3c51b793 100644 --- a/session/sess_redis.go +++ b/session/sess_redis.go @@ -11,10 +11,12 @@ import ( var redispder = &RedisProvider{} +// redis max pool size var MAX_POOL_SIZE = 100 var redisPool chan redis.Conn +// redis session store type RedisSessionStore struct { p *redis.Pool sid string @@ -23,6 +25,7 @@ type RedisSessionStore struct { maxlifetime int64 } +// set value in redis session func (rs *RedisSessionStore) Set(key, value interface{}) error { rs.lock.Lock() defer rs.lock.Unlock() @@ -30,6 +33,7 @@ func (rs *RedisSessionStore) Set(key, value interface{}) error { return nil } +// get value in redis session func (rs *RedisSessionStore) Get(key interface{}) interface{} { rs.lock.RLock() defer rs.lock.RUnlock() @@ -41,6 +45,7 @@ func (rs *RedisSessionStore) Get(key interface{}) interface{} { return nil } +// delete value in redis session func (rs *RedisSessionStore) Delete(key interface{}) error { rs.lock.Lock() defer rs.lock.Unlock() @@ -48,6 +53,7 @@ func (rs *RedisSessionStore) Delete(key interface{}) error { return nil } +// clear all values in redis session func (rs *RedisSessionStore) Flush() error { rs.lock.Lock() defer rs.lock.Unlock() @@ -55,10 +61,12 @@ func (rs *RedisSessionStore) Flush() error { return nil } +// get redis session id func (rs *RedisSessionStore) SessionID() string { return rs.sid } +// save session values to redis func (rs *RedisSessionStore) SessionRelease(w http.ResponseWriter) { c := rs.p.Get() defer c.Close() @@ -77,6 +85,7 @@ func (rs *RedisSessionStore) SessionRelease(w http.ResponseWriter) { c.Do("SET", rs.sid, string(b), "EX", rs.maxlifetime) } +// redis session provider type RedisProvider struct { maxlifetime int64 savePath string @@ -85,8 +94,9 @@ type RedisProvider struct { poollist *redis.Pool } -//savepath like redisserveraddr,poolsize,password -//127.0.0.1:6379,100,astaxie +// init redis session +// savepath like redis server addr,pool size,password +// e.g. 127.0.0.1:6379,100,astaxie func (rp *RedisProvider) SessionInit(maxlifetime int64, savePath string) error { rp.maxlifetime = maxlifetime configs := strings.Split(savePath, ",") @@ -122,6 +132,7 @@ func (rp *RedisProvider) SessionInit(maxlifetime int64, savePath string) error { return nil } +// read redis session by sid func (rp *RedisProvider) SessionRead(sid string) (SessionStore, error) { c := rp.poollist.Get() defer c.Close() @@ -141,6 +152,7 @@ func (rp *RedisProvider) SessionRead(sid string) (SessionStore, error) { return rs, nil } +// check redis session exist by sid func (rp *RedisProvider) SessionExist(sid string) bool { c := rp.poollist.Get() defer c.Close() @@ -152,6 +164,7 @@ func (rp *RedisProvider) SessionExist(sid string) bool { } } +// generate new sid for redis session func (rp *RedisProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) { c := rp.poollist.Get() defer c.Close() @@ -181,6 +194,7 @@ func (rp *RedisProvider) SessionRegenerate(oldsid, sid string) (SessionStore, er return rs, nil } +// delete redis session by id func (rp *RedisProvider) SessionDestroy(sid string) error { c := rp.poollist.Get() defer c.Close() @@ -189,11 +203,12 @@ func (rp *RedisProvider) SessionDestroy(sid string) error { return nil } +// Impelment method, no used. func (rp *RedisProvider) SessionGC() { return } -//@todo +// @todo func (rp *RedisProvider) SessionAll() int { return 0 } From 1d08a54f449410740969c8f78c1fd26912b38688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=82=85=E5=B0=8F=E9=BB=91?= Date: Wed, 29 Jan 2014 19:12:00 +0800 Subject: [PATCH 10/11] add comments for toolbox packages --- toolbox/debug.go | 20 ++++------ toolbox/healthcheck.go | 3 ++ toolbox/profile.go | 7 +++- toolbox/statistics.go | 6 +++ toolbox/task.go | 85 ++++++++++++++++++++++++++---------------- 5 files changed, 75 insertions(+), 46 deletions(-) diff --git a/toolbox/debug.go b/toolbox/debug.go index 7cb9db4e..c22af04f 100644 --- a/toolbox/debug.go +++ b/toolbox/debug.go @@ -29,16 +29,13 @@ type pointerInfo struct { used []int } -// // print the data in console -// func Display(data ...interface{}) { display(true, data...) } -// -// return string -// + +// return data print string func GetDisplayString(data ...interface{}) string { return display(false, data...) } @@ -67,9 +64,7 @@ func display(displayed bool, data ...interface{}) string { return buf.String() } -// -// return fomateinfo -// +// return data dump and format bytes func fomateinfo(headlen int, data ...interface{}) []byte { var buf = new(bytes.Buffer) @@ -108,6 +103,7 @@ func fomateinfo(headlen int, data ...interface{}) []byte { return buf.Bytes() } +// check data is golang basic type func isSimpleType(val reflect.Value, kind reflect.Kind, pointers **pointerInfo, interfaces *[]reflect.Value) bool { switch kind { case reflect.Bool: @@ -158,6 +154,7 @@ func isSimpleType(val reflect.Value, kind reflect.Kind, pointers **pointerInfo, return false } +// dump value func printKeyValue(buf *bytes.Buffer, val reflect.Value, pointers **pointerInfo, interfaces *[]reflect.Value, structFilter func(string, string) bool, formatOutput bool, indent string, level int) { var t = val.Kind() @@ -367,6 +364,7 @@ func printKeyValue(buf *bytes.Buffer, val reflect.Value, pointers **pointerInfo, } } +// dump pointer value func printPointerInfo(buf *bytes.Buffer, headlen int, pointers *pointerInfo) { var anyused = false var pointerNum = 0 @@ -434,9 +432,7 @@ func printPointerInfo(buf *bytes.Buffer, headlen int, pointers *pointerInfo) { } } -// -// get stack info -// +// get stack bytes func stack(skip int, indent string) []byte { var buf = new(bytes.Buffer) @@ -455,7 +451,7 @@ func stack(skip int, indent string) []byte { return buf.Bytes() } -// function returns, if possible, the name of the function containing the PC. +// return the name of the function containing the PC if possible, func function(pc uintptr) []byte { fn := runtime.FuncForPC(pc) if fn == nil { diff --git a/toolbox/healthcheck.go b/toolbox/healthcheck.go index 224624ad..1540c7c7 100644 --- a/toolbox/healthcheck.go +++ b/toolbox/healthcheck.go @@ -13,12 +13,15 @@ package toolbox //AddHealthCheck("database",&DatabaseCheck{}) +// health checker map var AdminCheckList map[string]HealthChecker +// health checker interface type HealthChecker interface { Check() error } +// add health checker with name string func AddHealthCheck(name string, hc HealthChecker) { AdminCheckList[name] = hc } diff --git a/toolbox/profile.go b/toolbox/profile.go index 07d55d26..41721b09 100644 --- a/toolbox/profile.go +++ b/toolbox/profile.go @@ -19,6 +19,7 @@ func init() { pid = os.Getpid() } +// parse input command string func ProcessInput(input string, w io.Writer) { switch input { case "lookup goroutine": @@ -44,6 +45,7 @@ func ProcessInput(input string, w io.Writer) { } } +// record memory profile in pprof func MemProf() { if f, err := os.Create("mem-" + strconv.Itoa(pid) + ".memprof"); err != nil { log.Fatal("record memory profile failed: %v", err) @@ -54,6 +56,7 @@ func MemProf() { } } +// start cpu profile monitor func StartCPUProfile() { f, err := os.Create("cpu-" + strconv.Itoa(pid) + ".pprof") if err != nil { @@ -62,10 +65,12 @@ func StartCPUProfile() { pprof.StartCPUProfile(f) } +// stop cpu profile monitor func StopCPUProfile() { pprof.StopCPUProfile() } +// print gc information to io.Writer func PrintGCSummary(w io.Writer) { memStats := &runtime.MemStats{} runtime.ReadMemStats(memStats) @@ -114,7 +119,7 @@ func avg(items []time.Duration) time.Duration { return time.Duration(int64(sum) / int64(len(items))) } -// human readable format +// format bytes number friendly func toH(bytes uint64) string { switch { case bytes < 1024: diff --git a/toolbox/statistics.go b/toolbox/statistics.go index b75cfb05..6042197e 100644 --- a/toolbox/statistics.go +++ b/toolbox/statistics.go @@ -7,6 +7,7 @@ import ( "time" ) +// Statistics struct type Statistics struct { RequestUrl string RequestController string @@ -16,12 +17,15 @@ type Statistics struct { TotalTime time.Duration } +// UrlMap contains several statistics struct to log different data type UrlMap struct { lock sync.RWMutex LengthLimit int //limit the urlmap's length if it's equal to 0 there's no limit urlmap map[string]map[string]*Statistics } +// add statistics task. +// it needs request method, request url, request controller and statistics time duration func (m *UrlMap) AddStatistics(requestMethod, requestUrl, requestController string, requesttime time.Duration) { m.lock.Lock() defer m.lock.Unlock() @@ -65,6 +69,7 @@ func (m *UrlMap) AddStatistics(requestMethod, requestUrl, requestController stri } } +// put url statistics result in io.Writer func (m *UrlMap) GetMap(rw io.Writer) { m.lock.RLock() defer m.lock.RUnlock() @@ -78,6 +83,7 @@ func (m *UrlMap) GetMap(rw io.Writer) { } } +// global statistics data map var StatisticsMap *UrlMap func init() { diff --git a/toolbox/task.go b/toolbox/task.go index ce9f56f9..b5558c15 100644 --- a/toolbox/task.go +++ b/toolbox/task.go @@ -53,6 +53,7 @@ const ( starBit = 1 << 63 ) +// time taks schedule type Schedule struct { Second uint64 Minute uint64 @@ -62,8 +63,10 @@ type Schedule struct { Week uint64 } +// task func type type TaskFunc func() error +// task interface type Tasker interface { GetStatus() string Run() error @@ -73,21 +76,24 @@ type Tasker interface { GetPrev() time.Time } +// task error type taskerr struct { t time.Time errinfo string } +// task struct type Task struct { Taskname string Spec *Schedule DoFunc TaskFunc Prev time.Time Next time.Time - Errlist []*taskerr //errtime:errinfo - ErrLimit int //max length for the errlist 0 stand for there' no limit + Errlist []*taskerr // like errtime:errinfo + ErrLimit int // max length for the errlist, 0 stand for no limit } +// add new task with name, time and func func NewTask(tname string, spec string, f TaskFunc) *Task { task := &Task{ @@ -99,6 +105,7 @@ func NewTask(tname string, spec string, f TaskFunc) *Task { return task } +// get current task status func (tk *Task) GetStatus() string { var str string for _, v := range tk.Errlist { @@ -107,6 +114,7 @@ func (tk *Task) GetStatus() string { return str } +// run task func (tk *Task) Run() error { err := tk.DoFunc() if err != nil { @@ -117,53 +125,58 @@ func (tk *Task) Run() error { return err } +// set next time for this task func (tk *Task) SetNext(now time.Time) { tk.Next = tk.Spec.Next(now) } +// get the next call time of this task func (tk *Task) GetNext() time.Time { return tk.Next } + +// set prev time of this task func (tk *Task) SetPrev(now time.Time) { tk.Prev = now } +// get prev time of this task func (tk *Task) GetPrev() time.Time { return tk.Prev } -//前6个字段分别表示: -// 秒钟:0-59 -// 分钟:0-59 -// 小时:1-23 -// 日期:1-31 -// 月份:1-12 -// 星期:0-6(0表示周日) +// six columns mean: +// second:0-59 +// minute:0-59 +// hour:1-23 +// day:1-31 +// month:1-12 +// week:0-6(0 means Sunday) -//还可以用一些特殊符号: -// *: 表示任何时刻 -// ,: 表示分割,如第三段里:2,4,表示2点和4点执行 -//   -:表示一个段,如第三端里: 1-5,就表示1到5点 -// /n : 表示每个n的单位执行一次,如第三段里,*/1, 就表示每隔1个小时执行一次命令。也可以写成1-23/1. +// some signals: +// *: any time +// ,:  separate signal +//   -:duration +// /n : do as n times of time duration ///////////////////////////////////////////////////////// -// 0/30 * * * * * 每30秒 执行 -// 0 43 21 * * * 21:43 执行 -// 0 15 05 * * *    05:15 执行 -// 0 0 17 * * * 17:00 执行 -// 0 0 17 * * 1 每周一的 17:00 执行 -// 0 0,10 17 * * 0,2,3 每周日,周二,周三的 17:00和 17:10 执行 -// 0 0-10 17 1 * * 毎月1日从 17:00到7:10 毎隔1分钟 执行 -// 0 0 0 1,15 * 1 毎月1日和 15日和 一日的 0:00 执行 -// 0 42 4 1 * *     毎月1日的 4:42分 执行 -// 0 0 21 * * 1-6   周一到周六 21:00 执行 -// 0 0,10,20,30,40,50 * * * *  每隔10分 执行 -// 0 */10 * * * *        每隔10分 执行 -// 0 * 1 * * *         从1:0到1:59 每隔1分钟 执行 -// 0 0 1 * * *         1:00 执行 -// 0 0 */1 * * *        毎时0分 每隔1小时 执行 -// 0 0 * * * *         毎时0分 每隔1小时 执行 -// 0 2 8-20/3 * * *       8:02,11:02,14:02,17:02,20:02 执行 -// 0 30 5 1,15 * *       1日 和 15日的 5:30 执行 +// 0/30 * * * * * every 30s +// 0 43 21 * * * 21:43 +// 0 15 05 * * *    05:15 +// 0 0 17 * * * 17:00 +// 0 0 17 * * 1 17:00 in every Monday +// 0 0,10 17 * * 0,2,3 17:00 and 17:10 in every Sunday, Tuesday and Wednesday +// 0 0-10 17 1 * * 17:00 to 17:10 in 1 min duration each time on the first day of month +// 0 0 0 1,15 * 1 0:00 on the 1st day and 15th day of month +// 0 42 4 1 * *     4:42 on the 1st day of month +// 0 0 21 * * 1-6   21:00 from Monday to Saturday +// 0 0,10,20,30,40,50 * * * *  every 10 min duration +// 0 */10 * * * *        every 10 min duration +// 0 * 1 * * *         1:00 to 1:59 in 1 min duration each time +// 0 0 1 * * *         1:00 +// 0 0 */1 * * *        0 min of hour in 1 hour duration +// 0 0 * * * *         0 min of hour in 1 hour duration +// 0 2 8-20/3 * * *       8:02, 11:02, 14:02, 17:02, 20:02 +// 0 30 5 1,15 * *       5:30 on the 1st day and 15th day of month func (t *Task) SetCron(spec string) { t.Spec = t.parse(spec) } @@ -252,6 +265,7 @@ func (t *Task) parseSpec(spec string) *Schedule { return nil } +// set schedule to next time func (s *Schedule) Next(t time.Time) time.Time { // Start at the earliest possible time (the upcoming second). @@ -349,6 +363,7 @@ func dayMatches(s *Schedule, t time.Time) bool { return domMatch || dowMatch } +// start all tasks func StartTask() { go run() } @@ -388,20 +403,23 @@ func run() { } } +// start all tasks func StopTask() { stop <- true } +// add task with name func AddTask(taskname string, t Tasker) { AdminTaskList[taskname] = t } -//sort map for tasker +// sort map for tasker type MapSorter struct { Keys []string Vals []Tasker } +// create new tasker map func NewMapSorter(m map[string]Tasker) *MapSorter { ms := &MapSorter{ Keys: make([]string, 0, len(m)), @@ -414,6 +432,7 @@ func NewMapSorter(m map[string]Tasker) *MapSorter { return ms } +// sort tasker map func (ms *MapSorter) Sort() { sort.Sort(ms) } From bbc71142d7000293d48a6bbb383f3be948deb933 Mon Sep 17 00:00:00 2001 From: "asta.xie" Date: Fri, 7 Feb 2014 00:38:58 +0800 Subject: [PATCH 11/11] controller can controller whether render the template. EnableReander default is true. --- controller.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/controller.go b/controller.go index 83aaacfc..76061a81 100644 --- a/controller.go +++ b/controller.go @@ -45,6 +45,7 @@ type Controller struct { CruSession session.SessionStore XSRFExpire int AppController interface{} + EnableReander bool } // ControllerInterface is an interface to uniform all controller handler. @@ -74,6 +75,7 @@ func (c *Controller) Init(ctx *context.Context, controllerName, actionName strin c.Ctx = ctx c.TplExt = "tpl" c.AppController = app + c.EnableReander = true } // Prepare runs after Init before request function execution. @@ -123,6 +125,9 @@ func (c *Controller) Options() { // Render sends the response with rendered template bytes as text/html type. func (c *Controller) Render() error { + if !c.EnableReander { + return nil + } rb, err := c.RenderBytes() if err != nil {