├── orm ├── types.go ├── utils.go ├── orm.go ├── README.md └── repo.go ├── str ├── md5.go ├── replace.go ├── rand.go └── regexp.go ├── .gitignore ├── file ├── write.go ├── read.go └── file.go ├── pool ├── gobrpc.go ├── jsonrpc.go ├── connpools.go └── connpool.go ├── conv └── conv.go ├── errors └── errors.go ├── param ├── decode.go └── param.go ├── format ├── time.go └── size.go ├── page └── paginator.go └── log ├── console.go ├── file.go └── log.go /orm/types.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | // G is shortcut 4 | type G map[string]interface{} 5 | -------------------------------------------------------------------------------- /str/md5.go: -------------------------------------------------------------------------------- 1 | package str 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | ) 7 | 8 | func MD5(s string) string { 9 | h := md5.New() 10 | h.Write([]byte(s)) 11 | return fmt.Sprintf("%x", h.Sum(nil)) 12 | } 13 | -------------------------------------------------------------------------------- /str/replace.go: -------------------------------------------------------------------------------- 1 | package str 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | func ENSymbol(raw string) string { 8 | raw = strings.Replace(raw, ",", ",", -1) 9 | raw = strings.Replace(raw, "(", "(", -1) 10 | raw = strings.Replace(raw, ")", ")", -1) 11 | raw = strings.Replace(raw, ":", ":", -1) 12 | raw = strings.Replace(raw, "。", ".", -1) 13 | return raw 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | *.sw[po] 6 | 7 | # Folders 8 | _obj 9 | _test 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.exe 24 | *.test 25 | *.prof 26 | -------------------------------------------------------------------------------- /file/write.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "os" 5 | "path" 6 | ) 7 | 8 | func WriteBytes(filePath string, b []byte) (int, error) { 9 | os.MkdirAll(path.Dir(filePath), os.ModePerm) 10 | fw, err := os.Create(filePath) 11 | if err != nil { 12 | return 0, err 13 | } 14 | defer fw.Close() 15 | return fw.Write(b) 16 | } 17 | 18 | func WriteString(filePath string, s string) (int, error) { 19 | return WriteBytes(filePath, []byte(s)) 20 | } 21 | -------------------------------------------------------------------------------- /orm/utils.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import "bytes" 4 | 5 | func snakeToUpperCamel(s string) string { 6 | buf := new(bytes.Buffer) 7 | first := true 8 | for i := 0; i < len(s); i++ { 9 | c := s[i] 10 | if c >= 'a' && c <= 'z' && first { 11 | buf.WriteByte(c - 32) 12 | first = false 13 | } else if c == '_' { 14 | first = true 15 | continue 16 | } else { 17 | buf.WriteByte(c) 18 | } 19 | } 20 | return buf.String() 21 | } 22 | -------------------------------------------------------------------------------- /pool/gobrpc.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "net/rpc" 7 | "time" 8 | ) 9 | 10 | func NewGobRPCPool(addr string, ct time.Duration, max, idle int) *ConnPool { 11 | p := NewConnPool(addr, max, idle) 12 | 13 | p.NewConn = func() (io.Closer, error) { 14 | conn, err := net.DialTimeout("tcp", p.Addr, ct) 15 | if err != nil { 16 | return nil, err 17 | } 18 | return rpc.NewClient(conn), nil 19 | } 20 | 21 | return p 22 | } 23 | -------------------------------------------------------------------------------- /pool/jsonrpc.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "net/rpc/jsonrpc" 7 | "time" 8 | ) 9 | 10 | func NewJSONRPCPool(addr string, ct time.Duration, max, idle int) *ConnPool { 11 | p := NewConnPool(addr, max, idle) 12 | 13 | p.NewConn = func() (io.Closer, error) { 14 | conn, err := net.DialTimeout("tcp", p.Addr, ct) 15 | if err != nil { 16 | return nil, err 17 | } 18 | return jsonrpc.NewClient(conn), nil 19 | } 20 | 21 | return p 22 | } 23 | -------------------------------------------------------------------------------- /conv/conv.go: -------------------------------------------------------------------------------- 1 | package conv 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // convert any numeric value to int64 9 | func ToInt64(value interface{}) (d int64, err error) { 10 | val := reflect.ValueOf(value) 11 | switch value.(type) { 12 | case int, int8, int16, int32, int64: 13 | d = val.Int() 14 | case uint, uint8, uint16, uint32, uint64: 15 | d = int64(val.Uint()) 16 | default: 17 | err = fmt.Errorf("ToInt64 need numeric not `%T`", value) 18 | } 19 | return 20 | } 21 | -------------------------------------------------------------------------------- /errors/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import "fmt" 4 | 5 | type PageError struct { 6 | Message string 7 | } 8 | 9 | func Bomb(format string, a ...interface{}) { 10 | panic(PageError{Message: fmt.Sprintf(format, a...)}) 11 | } 12 | 13 | func Dangerous(v interface{}) { 14 | if v == nil { 15 | return 16 | } 17 | 18 | switch t := v.(type) { 19 | case string: 20 | if t != "" { 21 | panic(PageError{Message: t}) 22 | } 23 | case error: 24 | panic(PageError{Message: t.Error()}) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /param/decode.go: -------------------------------------------------------------------------------- 1 | package param 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | 8 | "github.com/ulricqin/go/errors" 9 | ) 10 | 11 | func ParseJSON(r *http.Request, v interface{}) { 12 | if r.ContentLength == 0 { 13 | errors.Bomb("content is blank") 14 | } 15 | 16 | if r.Body == nil { 17 | errors.Bomb("body is nil") 18 | } 19 | 20 | bs, err := ioutil.ReadAll(r.Body) 21 | if err != nil { 22 | errors.Bomb("cannot read body") 23 | } 24 | 25 | err = json.Unmarshal(bs, v) 26 | if err != nil { 27 | errors.Bomb("cannot decode body: %s", err.Error()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /str/rand.go: -------------------------------------------------------------------------------- 1 | package str 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") 10 | var digits = []rune("0123456789") 11 | 12 | const size = 62 13 | 14 | func RandLetters(n int) string { 15 | seed := time.Now().UnixNano() 16 | rand.Seed(seed) 17 | 18 | b := make([]rune, n) 19 | for i := range b { 20 | b[i] = letters[rand.Intn(size)] 21 | } 22 | 23 | return fmt.Sprintf("%s", string(b)) 24 | } 25 | 26 | func RandDigits(n int) string { 27 | seed := time.Now().UnixNano() 28 | rand.Seed(seed) 29 | 30 | b := make([]rune, n) 31 | for i := range b { 32 | b[i] = digits[rand.Intn(10)] 33 | } 34 | 35 | return fmt.Sprintf("%s", string(b)) 36 | } 37 | -------------------------------------------------------------------------------- /format/time.go: -------------------------------------------------------------------------------- 1 | package format 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func HumanDuration(now, before int64) string { 8 | d := now - before 9 | if d <= 60 { 10 | return "just now" 11 | } 12 | 13 | if d <= 120 { 14 | return "1 minute ago" 15 | } 16 | 17 | if d <= 3600 { 18 | return fmt.Sprintf("%d minutes ago", d/60) 19 | } 20 | 21 | if d <= 7200 { 22 | return "1 hour ago" 23 | } 24 | 25 | if d <= 3600*24 { 26 | return fmt.Sprintf("%d hours ago", d/3600) 27 | } 28 | 29 | if d <= 3600*24*2 { 30 | return "1 day ago" 31 | } 32 | 33 | return fmt.Sprintf("%d days ago", d/3600/24) 34 | } 35 | 36 | func Time(ts int64, pattern ...string) string { 37 | def := "2006-01-02 15:04:05" 38 | if len(pattern) > 0 { 39 | def = pattern[0] 40 | } 41 | return time.Unix(ts, 0).Format(def) 42 | } 43 | -------------------------------------------------------------------------------- /format/size.go: -------------------------------------------------------------------------------- 1 | package format 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func HumanSize(raw float64) string { 8 | var t float64 = 1024 9 | var d float64 = 1 10 | 11 | if raw < t { 12 | return fmt.Sprintf("%.1fB", raw/d) 13 | } 14 | 15 | d *= 1024 16 | t *= 1024 17 | 18 | if raw < t { 19 | return fmt.Sprintf("%.1fK", raw/d) 20 | } 21 | 22 | d *= 1024 23 | t *= 1024 24 | 25 | if raw < t { 26 | return fmt.Sprintf("%.1fM", raw/d) 27 | } 28 | 29 | d *= 1024 30 | t *= 1024 31 | 32 | if raw < t { 33 | return fmt.Sprintf("%.1fG", raw/d) 34 | } 35 | 36 | d *= 1024 37 | t *= 1024 38 | 39 | if raw < t { 40 | return fmt.Sprintf("%.1fT", raw/d) 41 | } 42 | 43 | d *= 1024 44 | t *= 1024 45 | 46 | if raw < t { 47 | return fmt.Sprintf("%.1fP", raw/d) 48 | } 49 | 50 | return "TooLarge" 51 | } 52 | -------------------------------------------------------------------------------- /str/regexp.go: -------------------------------------------------------------------------------- 1 | package str 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | func IsMatch(s, pattern string) bool { 9 | match, err := regexp.Match(pattern, []byte(s)) 10 | if err != nil { 11 | return false 12 | } 13 | 14 | return match 15 | } 16 | 17 | func IsIdentifier(s string, pattern ...string) bool { 18 | defpattern := "^[a-zA-Z0-9\\-\\_\\.]+$" 19 | if len(pattern) > 0 { 20 | defpattern = pattern[0] 21 | } 22 | 23 | return IsMatch(s, defpattern) 24 | } 25 | 26 | func IsMail(s string) bool { 27 | return IsMatch(s, `\w[-._\w]*@\w[-._\w]*\.\w+`) 28 | } 29 | 30 | func IsPhone(s string) bool { 31 | if strings.HasPrefix(s, "+") { 32 | return IsMatch(s[1:], `\d{13}`) 33 | } else { 34 | return IsMatch(s, `\d{11}`) 35 | } 36 | } 37 | 38 | func Dangerous(s string) bool { 39 | if strings.Contains(s, "<") { 40 | return true 41 | } 42 | 43 | if strings.Contains(s, ">") { 44 | return true 45 | } 46 | 47 | if strings.Contains(s, "&") { 48 | return true 49 | } 50 | 51 | if strings.Contains(s, "'") { 52 | return true 53 | } 54 | 55 | if strings.Contains(s, "\"") { 56 | return true 57 | } 58 | 59 | return false 60 | } 61 | -------------------------------------------------------------------------------- /pool/connpools.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type ConnPools struct { 8 | sync.RWMutex 9 | pools map[string]*ConnPool 10 | } 11 | 12 | func (cps *ConnPools) Has(instance string) bool { 13 | cps.RLock() 14 | _, has := cps.pools[instance] 15 | cps.RUnlock() 16 | return has 17 | } 18 | 19 | func (cps *ConnPools) Get(instance string) (*ConnPool, bool) { 20 | cps.RLock() 21 | p, has := cps.pools[instance] 22 | cps.RUnlock() 23 | return p, has 24 | } 25 | 26 | func (cps *ConnPools) Put(connPool *ConnPool) { 27 | cps.Lock() 28 | cps.pools[connPool.Addr] = connPool 29 | cps.Unlock() 30 | } 31 | 32 | func (cps *ConnPools) Size() int { 33 | cps.RLock() 34 | l := len(cps.pools) 35 | cps.RUnlock() 36 | return l 37 | } 38 | 39 | func (cps *ConnPools) Keys() []string { 40 | i := 0 41 | cps.RLock() 42 | keys := make([]string, len(cps.pools)) 43 | for key := range cps.pools { 44 | keys[i] = key 45 | } 46 | cps.RUnlock() 47 | return keys 48 | } 49 | 50 | func (cps *ConnPools) Evict(instance string) { 51 | cps.Lock() 52 | p, has := cps.pools[instance] 53 | if !has { 54 | cps.Unlock() 55 | return 56 | } 57 | delete(cps.pools, instance) 58 | cps.Unlock() 59 | p.Clean() 60 | } 61 | -------------------------------------------------------------------------------- /file/read.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "bufio" 5 | "io/ioutil" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | func ToBytes(filePath string) ([]byte, error) { 11 | return ioutil.ReadFile(filePath) 12 | } 13 | 14 | func ToString(filePath string) (string, error) { 15 | b, err := ioutil.ReadFile(filePath) 16 | if err != nil { 17 | return "", err 18 | } 19 | return string(b), nil 20 | } 21 | 22 | func ToTrimString(filePath string) (string, error) { 23 | str, err := ToString(filePath) 24 | if err != nil { 25 | return "", err 26 | } 27 | 28 | return strings.TrimSpace(str), nil 29 | } 30 | 31 | func ToUint64(filePath string) (uint64, error) { 32 | content, err := ToTrimString(filePath) 33 | if err != nil { 34 | return 0, err 35 | } 36 | 37 | var ret uint64 38 | if ret, err = strconv.ParseUint(content, 10, 64); err != nil { 39 | return 0, err 40 | } 41 | return ret, nil 42 | } 43 | 44 | func ToInt64(filePath string) (int64, error) { 45 | content, err := ToTrimString(filePath) 46 | if err != nil { 47 | return 0, err 48 | } 49 | 50 | var ret int64 51 | if ret, err = strconv.ParseInt(content, 10, 64); err != nil { 52 | return 0, err 53 | } 54 | return ret, nil 55 | } 56 | 57 | func ReadLine(r *bufio.Reader) ([]byte, error) { 58 | line, isPrefix, err := r.ReadLine() 59 | for isPrefix && err == nil { 60 | var bs []byte 61 | bs, isPrefix, err = r.ReadLine() 62 | line = append(line, bs...) 63 | } 64 | 65 | return line, err 66 | } 67 | -------------------------------------------------------------------------------- /orm/orm.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "database/sql" 5 | "reflect" 6 | ) 7 | 8 | // Orm 一个程序创建一个全局的Orm对象即可 9 | type Orm struct { 10 | dbs map[string]*sql.DB 11 | mappings map[string]map[string]string 12 | ShowSQL bool 13 | } 14 | 15 | // New 创建全局的Orm对象 16 | func New() *Orm { 17 | return &Orm{ 18 | dbs: make(map[string]*sql.DB), 19 | mappings: make(map[string]map[string]string), 20 | ShowSQL: true, 21 | } 22 | } 23 | 24 | // Add 增加一个DataSource 25 | func (o *Orm) Add(name, addr string, idle, max int) error { 26 | db, err := sql.Open("mysql", addr) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | db.SetMaxIdleConns(idle) 32 | db.SetMaxOpenConns(max) 33 | 34 | o.dbs[name] = db 35 | return nil 36 | } 37 | 38 | // Register 注册Struct,程序启动的时候先进行Register 39 | // e.g. orm.New().Register(new(User), new(Topic)) 40 | func (o *Orm) Register(vs ...interface{}) { 41 | l := len(vs) 42 | for i := 0; i < l; i++ { 43 | typ := reflect.TypeOf(vs[i]) 44 | ele := typ.Elem() 45 | num := ele.NumField() 46 | fields := make(map[string]string) 47 | for j := 0; j < num; j++ { 48 | field := ele.Field(j) 49 | tag := field.Tag.Get("orm") 50 | if tag != "" { 51 | fields[tag] = field.Name 52 | } 53 | } 54 | o.mappings[typ.String()] = fields 55 | } 56 | } 57 | 58 | // NewRepo 创建一个Repo,每做一次SQL操作都要新new一个Repo 59 | func (o *Orm) NewRepo(tbl string) *Repo { 60 | return &Repo{ 61 | o: o, 62 | tbl: tbl, 63 | showSQL: o.ShowSQL, 64 | } 65 | } 66 | 67 | // Use 使用哪个数据库 68 | func (o *Orm) Use(name string) *sql.DB { 69 | db, has := o.dbs[name] 70 | if !has { 71 | panic("no such database: " + name) 72 | } 73 | return db 74 | } 75 | 76 | // Tag2field 通过tag查字段名称 77 | func (o *Orm) Tag2field(typ reflect.Type, key string) string { 78 | m, has := o.mappings[typ.String()] 79 | if !has { 80 | return snakeToUpperCamel(key) 81 | } 82 | 83 | val, has := m[key] 84 | if !has { 85 | return snakeToUpperCamel(key) 86 | } 87 | 88 | return val 89 | } 90 | -------------------------------------------------------------------------------- /pool/connpool.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "sync" 7 | ) 8 | 9 | // ErrMaxConn Maximum connections reached 10 | var ErrMaxConn = errors.New("maximum connections reached") 11 | 12 | // ConnPool manages the life cycle of connections 13 | type ConnPool struct { 14 | sync.RWMutex 15 | 16 | // NewConn is used to create a new connection if necessary. 17 | NewConn func() (io.Closer, error) 18 | 19 | Addr string 20 | Max int 21 | Idle int 22 | 23 | active int 24 | free []io.Closer 25 | conns map[io.Closer]struct{} 26 | } 27 | 28 | // NewConnPool create a connection pool 29 | func NewConnPool(addr string, max int, idle int) *ConnPool { 30 | return &ConnPool{ 31 | Addr: addr, 32 | Max: max, 33 | Idle: idle, 34 | conns: make(map[io.Closer]struct{}), 35 | } 36 | } 37 | 38 | // Get get a connection 39 | func (cp *ConnPool) Get() (conn io.Closer, err error) { 40 | conn = cp.tryFree() 41 | if conn != nil { 42 | return 43 | } 44 | 45 | if cp.overMax() { 46 | return nil, ErrMaxConn 47 | } 48 | 49 | conn, err = cp.NewConn() 50 | if err == nil { 51 | cp.inc() 52 | cp.conns[conn] = struct{}{} 53 | } 54 | 55 | return 56 | } 57 | 58 | // Clean close all connections 59 | func (cp *ConnPool) Clean() { 60 | cp.Lock() 61 | for conn := range cp.conns { 62 | if conn != nil { 63 | conn.Close() 64 | } 65 | } 66 | cp.active = 0 67 | cp.conns = nil 68 | cp.Unlock() 69 | } 70 | 71 | // Put recycle the connection 72 | func (cp *ConnPool) Put(conn io.Closer) { 73 | if cp.overIdle() { 74 | cp.Close(conn) 75 | } else { 76 | cp.Lock() 77 | cp.free = append(cp.free, conn) 78 | cp.Unlock() 79 | } 80 | } 81 | 82 | // Close close the connection 83 | func (cp *ConnPool) Close(conn io.Closer) { 84 | cp.dec() 85 | if conn != nil { 86 | conn.Close() 87 | } 88 | } 89 | 90 | func (cp *ConnPool) tryFree() io.Closer { 91 | cp.Lock() 92 | if len(cp.free) == 0 { 93 | cp.Unlock() 94 | return nil 95 | } 96 | 97 | conn := cp.free[0] 98 | cp.free = cp.free[1:] 99 | 100 | cp.Unlock() 101 | return conn 102 | } 103 | 104 | func (cp *ConnPool) overMax() bool { 105 | cp.RLock() 106 | over := cp.active >= cp.Max 107 | cp.RUnlock() 108 | return over 109 | } 110 | 111 | func (cp *ConnPool) overIdle() bool { 112 | cp.RLock() 113 | over := len(cp.free) >= cp.Idle 114 | cp.RUnlock() 115 | return over 116 | } 117 | 118 | func (cp *ConnPool) inc() { 119 | cp.Lock() 120 | cp.active++ 121 | cp.Unlock() 122 | } 123 | 124 | func (cp *ConnPool) dec() { 125 | cp.Lock() 126 | cp.active-- 127 | cp.Unlock() 128 | } 129 | -------------------------------------------------------------------------------- /page/paginator.go: -------------------------------------------------------------------------------- 1 | package page 2 | 3 | import ( 4 | "math" 5 | "net/http" 6 | "net/url" 7 | "strconv" 8 | 9 | "github.com/ulricqin/go/conv" 10 | ) 11 | 12 | type Paginator struct { 13 | Request *http.Request 14 | PerPageNums int 15 | MaxPages int 16 | 17 | nums int64 18 | pageRange []int 19 | pageNums int 20 | page int 21 | } 22 | 23 | func (p *Paginator) PageNums() int { 24 | if p.pageNums != 0 { 25 | return p.pageNums 26 | } 27 | pageNums := math.Ceil(float64(p.nums) / float64(p.PerPageNums)) 28 | if p.MaxPages > 0 { 29 | pageNums = math.Min(pageNums, float64(p.MaxPages)) 30 | } 31 | p.pageNums = int(pageNums) 32 | return p.pageNums 33 | } 34 | 35 | func (p *Paginator) Nums() int64 { 36 | return p.nums 37 | } 38 | 39 | func (p *Paginator) SetNums(nums interface{}) { 40 | p.nums, _ = conv.ToInt64(nums) 41 | } 42 | 43 | func (p *Paginator) Page() int { 44 | if p.page != 0 { 45 | return p.page 46 | } 47 | if p.Request.Form == nil { 48 | p.Request.ParseForm() 49 | } 50 | p.page, _ = strconv.Atoi(p.Request.Form.Get("p")) 51 | if p.page > p.PageNums() { 52 | p.page = p.PageNums() 53 | } 54 | if p.page <= 0 { 55 | p.page = 1 56 | } 57 | return p.page 58 | } 59 | 60 | func (p *Paginator) Pages() []int { 61 | if p.pageRange == nil && p.nums > 0 { 62 | var pages []int 63 | pageNums := p.PageNums() 64 | page := p.Page() 65 | switch { 66 | case page >= pageNums-4 && pageNums > 9: 67 | start := pageNums - 9 + 1 68 | pages = make([]int, 9) 69 | for i, _ := range pages { 70 | pages[i] = start + i 71 | } 72 | case page >= 5 && pageNums > 9: 73 | start := page - 5 + 1 74 | pages = make([]int, int(math.Min(9, float64(page+4+1)))) 75 | for i, _ := range pages { 76 | pages[i] = start + i 77 | } 78 | default: 79 | pages = make([]int, int(math.Min(9, float64(pageNums)))) 80 | for i, _ := range pages { 81 | pages[i] = i + 1 82 | } 83 | } 84 | p.pageRange = pages 85 | } 86 | return p.pageRange 87 | } 88 | 89 | func (p *Paginator) PageLink(page int) string { 90 | link, _ := url.ParseRequestURI(p.Request.RequestURI) 91 | values := link.Query() 92 | if page == 1 { 93 | values.Del("p") 94 | } else { 95 | values.Set("p", strconv.Itoa(page)) 96 | } 97 | link.RawQuery = values.Encode() 98 | return link.String() 99 | } 100 | 101 | func (p *Paginator) PageLinkPrev() (link string) { 102 | if p.HasPrev() { 103 | link = p.PageLink(p.Page() - 1) 104 | } 105 | return 106 | } 107 | 108 | func (p *Paginator) PageLinkNext() (link string) { 109 | if p.HasNext() { 110 | link = p.PageLink(p.Page() + 1) 111 | } 112 | return 113 | } 114 | 115 | func (p *Paginator) PageLinkFirst() (link string) { 116 | return p.PageLink(1) 117 | } 118 | 119 | func (p *Paginator) PageLinkLast() (link string) { 120 | return p.PageLink(p.PageNums()) 121 | } 122 | 123 | func (p *Paginator) HasPrev() bool { 124 | return p.Page() > 1 125 | } 126 | 127 | func (p *Paginator) HasNext() bool { 128 | return p.Page() < p.PageNums() 129 | } 130 | 131 | func (p *Paginator) IsActive(page int) bool { 132 | return p.Page() == page 133 | } 134 | 135 | func (p *Paginator) Offset() int { 136 | return (p.Page() - 1) * p.PerPageNums 137 | } 138 | 139 | func (p *Paginator) HasPages() bool { 140 | return p.PageNums() > 1 141 | } 142 | 143 | func NewPaginator(req *http.Request, per int, nums interface{}) *Paginator { 144 | p := Paginator{} 145 | p.Request = req 146 | if per <= 0 { 147 | per = 10 148 | } 149 | p.PerPageNums = per 150 | p.SetNums(nums) 151 | return &p 152 | } 153 | -------------------------------------------------------------------------------- /param/param.go: -------------------------------------------------------------------------------- 1 | package param 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/ulricqin/go/errors" 9 | ) 10 | 11 | func String(r *http.Request, key string, defVal string) string { 12 | if val, ok := r.URL.Query()[key]; ok { 13 | if val[0] == "" { 14 | return defVal 15 | } 16 | return strings.TrimSpace(val[0]) 17 | } 18 | 19 | if r.Form == nil { 20 | errors.Dangerous(r.ParseForm()) 21 | } 22 | 23 | val := r.Form.Get(key) 24 | if val == "" { 25 | return defVal 26 | } 27 | 28 | return strings.TrimSpace(val) 29 | } 30 | 31 | func MustString(r *http.Request, key string, displayName ...string) string { 32 | val := String(r, key, "") 33 | if val == "" { 34 | name := key 35 | if len(displayName) > 0 { 36 | name = displayName[0] 37 | } 38 | errors.Bomb("%s is necessary", name) 39 | } 40 | return val 41 | } 42 | 43 | func Int64(r *http.Request, key string, defVal int64) int64 { 44 | raw := String(r, key, "") 45 | if raw == "" { 46 | return defVal 47 | } 48 | 49 | val, err := strconv.ParseInt(raw, 10, 64) 50 | if err != nil { 51 | return defVal 52 | } 53 | 54 | return val 55 | } 56 | 57 | func MustInt64(r *http.Request, key string, displayName ...string) int64 { 58 | raw := String(r, key, "") 59 | if raw == "" { 60 | name := key 61 | if len(displayName) > 0 { 62 | name = displayName[0] 63 | } 64 | errors.Bomb("%s is necessary", name) 65 | } 66 | 67 | val, err := strconv.ParseInt(raw, 10, 64) 68 | errors.Dangerous(err) 69 | 70 | return val 71 | } 72 | 73 | func Int(r *http.Request, key string, defVal int) int { 74 | raw := String(r, key, "") 75 | if raw == "" { 76 | return defVal 77 | } 78 | 79 | val, err := strconv.Atoi(raw) 80 | if err != nil { 81 | return defVal 82 | } 83 | 84 | return val 85 | } 86 | 87 | func MustInt(r *http.Request, key string, displayName ...string) int { 88 | name := key 89 | if len(displayName) > 0 { 90 | name = displayName[0] 91 | } 92 | 93 | raw := String(r, key, "") 94 | if raw == "" { 95 | errors.Bomb("%s is necessary", name) 96 | } 97 | 98 | val, err := strconv.Atoi(raw) 99 | if err != nil { 100 | errors.Bomb("%s should be integer", name) 101 | } 102 | 103 | return val 104 | } 105 | 106 | func Float64(r *http.Request, key string, defVal float64) float64 { 107 | raw := String(r, key, "") 108 | if raw == "" { 109 | return defVal 110 | } 111 | 112 | val, err := strconv.ParseFloat(raw, 64) 113 | if err != nil { 114 | return defVal 115 | } 116 | 117 | return val 118 | } 119 | 120 | func MustFloat64(r *http.Request, key string, displayName ...string) float64 { 121 | raw := String(r, key, "") 122 | if raw == "" { 123 | name := key 124 | if len(displayName) > 0 { 125 | name = displayName[0] 126 | } 127 | errors.Bomb("%s is necessary", name) 128 | } 129 | 130 | val, err := strconv.ParseFloat(raw, 64) 131 | errors.Dangerous(err) 132 | 133 | return val 134 | } 135 | 136 | func Bool(r *http.Request, key string, defVal bool) bool { 137 | raw := String(r, key, "") 138 | if raw == "true" || raw == "1" || raw == "on" || raw == "checked" || raw == "yes" { 139 | return true 140 | } else if raw == "false" || raw == "0" || raw == "off" || raw == "" || raw == "no" { 141 | return false 142 | } else { 143 | return defVal 144 | } 145 | } 146 | 147 | func MustBool(r *http.Request, key string) bool { 148 | raw := String(r, key, "") 149 | if raw == "true" || raw == "1" || raw == "on" || raw == "checked" || raw == "yes" { 150 | return true 151 | } else if raw == "false" || raw == "0" || raw == "off" || raw == "" || raw == "no" { 152 | return false 153 | } else { 154 | errors.Bomb("bad request") 155 | } 156 | 157 | return false 158 | } 159 | -------------------------------------------------------------------------------- /file/file.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "path" 9 | "path/filepath" 10 | "strings" 11 | ) 12 | 13 | // SelfPath gets compiled executable file absolute path 14 | func SelfPath() string { 15 | path, _ := filepath.Abs(os.Args[0]) 16 | return path 17 | } 18 | 19 | // get absolute filepath, based on built executable file 20 | func AbsPath(fp string) (string, error) { 21 | return filepath.Abs(fp) 22 | } 23 | 24 | // SelfDir gets compiled executable file directory 25 | func SelfDir() string { 26 | return filepath.Dir(SelfPath()) 27 | } 28 | 29 | // get filepath base name 30 | func Basename(fp string) string { 31 | return path.Base(fp) 32 | } 33 | 34 | // get filepath dir name 35 | func Dir(fp string) string { 36 | return path.Dir(fp) 37 | } 38 | 39 | func EnsureDir(fp string) error { 40 | return os.MkdirAll(fp, os.ModePerm) 41 | } 42 | 43 | // create one file 44 | func Create(name string) (*os.File, error) { 45 | return os.Create(name) 46 | } 47 | 48 | func Ext(fp string) string { 49 | return path.Ext(fp) 50 | } 51 | 52 | // rename file name 53 | func Rename(src string, target string) error { 54 | return os.Rename(src, target) 55 | } 56 | 57 | // delete file 58 | func Unlink(fp string) error { 59 | return os.Remove(fp) 60 | } 61 | 62 | // IsFile checks whether the path is a file, 63 | // it returns false when it's a directory or does not exist. 64 | func IsFile(fp string) bool { 65 | f, e := os.Stat(fp) 66 | if e != nil { 67 | return false 68 | } 69 | return !f.IsDir() 70 | } 71 | 72 | // IsExist checks whether a file or directory exists. 73 | // It returns false when the file or directory does not exist. 74 | func IsExist(fp string) bool { 75 | _, err := os.Stat(fp) 76 | return err == nil || os.IsExist(err) 77 | } 78 | 79 | // Search a file in paths. 80 | // this is often used in search config file in /etc ~/ 81 | func SearchFile(filename string, paths ...string) (fullPath string, err error) { 82 | for _, path := range paths { 83 | if fullPath = filepath.Join(path, filename); IsExist(fullPath) { 84 | return 85 | } 86 | } 87 | err = fmt.Errorf("%s not found in paths", fullPath) 88 | return 89 | } 90 | 91 | // get file modified time 92 | func FileMTime(fp string) (int64, error) { 93 | f, e := os.Stat(fp) 94 | if e != nil { 95 | return 0, e 96 | } 97 | return f.ModTime().Unix(), nil 98 | } 99 | 100 | // get file size as how many bytes 101 | func FileSize(fp string) (int64, error) { 102 | f, e := os.Stat(fp) 103 | if e != nil { 104 | return 0, e 105 | } 106 | return f.Size(), nil 107 | } 108 | 109 | // list dirs under dirPath 110 | func DirsUnder(dirPath string) ([]string, error) { 111 | if !IsExist(dirPath) { 112 | return []string{}, nil 113 | } 114 | 115 | fs, err := ioutil.ReadDir(dirPath) 116 | if err != nil { 117 | return []string{}, err 118 | } 119 | 120 | sz := len(fs) 121 | if sz == 0 { 122 | return []string{}, nil 123 | } 124 | 125 | ret := make([]string, 0, sz) 126 | for i := 0; i < sz; i++ { 127 | if fs[i].IsDir() { 128 | name := fs[i].Name() 129 | if name != "." && name != ".." { 130 | ret = append(ret, name) 131 | } 132 | } 133 | } 134 | 135 | return ret, nil 136 | } 137 | 138 | // list files under dirPath 139 | func FilesUnder(dirPath string) ([]string, error) { 140 | if !IsExist(dirPath) { 141 | return []string{}, nil 142 | } 143 | 144 | fs, err := ioutil.ReadDir(dirPath) 145 | if err != nil { 146 | return []string{}, err 147 | } 148 | 149 | sz := len(fs) 150 | if sz == 0 { 151 | return []string{}, nil 152 | } 153 | 154 | ret := make([]string, 0, sz) 155 | for i := 0; i < sz; i++ { 156 | if !fs[i].IsDir() { 157 | ret = append(ret, fs[i].Name()) 158 | } 159 | } 160 | 161 | return ret, nil 162 | } 163 | 164 | func MustOpenLogFile(fp string) *os.File { 165 | if strings.Contains(fp, "/") { 166 | dir := Dir(fp) 167 | err := EnsureDir(dir) 168 | if err != nil { 169 | log.Fatalf("mkdir -p %s occur error %v", dir, err) 170 | } 171 | } 172 | 173 | f, err := os.OpenFile(fp, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) 174 | if err != nil { 175 | log.Fatalf("open %s occur error %v", fp, err) 176 | } 177 | 178 | return f 179 | } 180 | -------------------------------------------------------------------------------- /orm/README.md: -------------------------------------------------------------------------------- 1 | *最简单的orm小框架,只支持mysql,下面是基本用法范例* 2 | 3 | ```go 4 | package main 5 | 6 | import ( 7 | "log" 8 | 9 | "github.com/ulricqin/orm" 10 | 11 | _ "github.com/go-sql-driver/mysql" 12 | ) 13 | 14 | // User 表在default库,即:minos_portal库 15 | type User struct { 16 | ID int64 `orm:"id"` 17 | Username string 18 | Nickname string 19 | } 20 | 21 | // UserRepo 这是每次DB操作的入口函数 22 | // user表在default库,不需要使用Use来特别指定 23 | func UserRepo() *orm.Repo { 24 | return Orm.NewRepo("user") 25 | } 26 | 27 | // Judge 在naming库,即:minos_naming库 28 | type Judge struct { 29 | ID int64 `orm:"id"` 30 | Address string `orm:"address"` 31 | } 32 | 33 | // JudgeRepo 这是每次DB操作的入口函数 34 | // judge表不在默认的default库,故而需要执行Use 35 | func JudgeRepo() *orm.Repo { 36 | return Orm.NewRepo("judge").Use("naming") 37 | } 38 | 39 | // DBConfig 数据库配置,支持配置多个库 40 | // 至少有个default库 41 | type DBConfig struct { 42 | Addr map[string]string 43 | Idle int 44 | Max int 45 | } 46 | 47 | var configs = DBConfig{ 48 | Addr: map[string]string{ 49 | "default": "root@tcp(127.0.0.1:3306)/minos_portal?charset=utf8&&loc=Asia%2FShanghai", 50 | "naming": "root@tcp(127.0.0.1:3306)/minos_naming?charset=utf8&&loc=Asia%2FShanghai", 51 | }, 52 | Idle: 2, 53 | Max: 10, 54 | } 55 | 56 | // Orm 全局操作入口 57 | var Orm *orm.Orm 58 | 59 | func main() { 60 | 61 | // Orm 对象可以放在程序全局,程序启动的时候初始化好 62 | Orm = orm.New() 63 | 64 | // 配置Orm的DataSource 65 | for k, v := range configs.Addr { 66 | if err := Orm.Add(k, v, configs.Idle, configs.Max); err != nil { 67 | // 程序启动的时候如果发现数据库连接不上,直接报错退出 68 | log.Fatalln(err) 69 | } 70 | } 71 | 72 | // 将各个model注册给Orm,这样才能识别Struct中各个字段的orm tag 73 | Orm.Register(new(User), new(Judge)) 74 | 75 | // 插入一条记录 76 | lastid, err := UserRepo().Insert(orm.G{ 77 | "username": "UlricQin", 78 | "nickname": "秦晓辉", 79 | }) 80 | dangerous(err) 81 | 82 | log.Println("insert user success, lastid:", lastid) 83 | 84 | // 查一条记录出来 85 | var user User 86 | has, err := UserRepo().Where("id=?", lastid).Find(&user) 87 | dangerous(err) 88 | 89 | if !has { 90 | log.Fatalln("no such user") 91 | } 92 | 93 | log.Println("Find user:", user) 94 | 95 | // 更新一条记录,如果调用了Quiet,将不打印sql语句 96 | num, err := UserRepo().Quiet().Where("id=?", lastid).Update(orm.G{ 97 | "username": "Ulric2", 98 | "nickname": "晓辉", 99 | }) 100 | dangerous(err) 101 | 102 | log.Println("update affected rows:", num) 103 | 104 | // 再插入一条记录,做个列表查询 105 | _, err = UserRepo().Insert(orm.G{ 106 | "username": "Ulric1", 107 | "nickname": "Flame", 108 | }) 109 | dangerous(err) 110 | 111 | // 计数 112 | count, err := UserRepo().Where("id>=?", lastid).Count() 113 | dangerous(err) 114 | 115 | log.Printf("user count of id>=%d is %d", lastid, count) 116 | 117 | // 只查询一列 118 | usernames, err := UserRepo().Where("id>=?", lastid).OrderBy("username").Limit(1, 1).StrCol("username") 119 | dangerous(err) 120 | 121 | log.Println("usernames, should only has Ulric2 => ", usernames) 122 | 123 | // 查询列表 124 | var users []*User 125 | err = UserRepo().Where("id>=?", lastid).Finds(&users) 126 | dangerous(err) 127 | 128 | log.Println("Find users:") 129 | for i := 0; i < len(users); i++ { 130 | log.Println(users[i]) 131 | } 132 | 133 | // 删除操作 134 | num, err = UserRepo().Limit(2).Where("id>=?", lastid).Delete() 135 | dangerous(err) 136 | 137 | log.Println("delete user affected:", num) 138 | 139 | log.Println("------------------") 140 | 141 | // 以上封装的方法都是针对单表的,这个简易orm框架也就只做这些事情 142 | // 复杂的sql操作可以直接使用内部的*sql.DB,比如 143 | 144 | ret, err := Orm.Use("naming").Exec("insert into judge(address, last_update) values(?, now())", "127.0.0.1:7788") 145 | dangerous(err) 146 | 147 | lastid, err = ret.LastInsertId() 148 | dangerous(err) 149 | 150 | log.Println("insert address success, lastid:", lastid) 151 | 152 | row := Orm.Use("naming").QueryRow("select address from judge where id = ?", lastid) 153 | var address string 154 | err = row.Scan(&address) 155 | dangerous(err) 156 | log.Println("query row address:", address) 157 | 158 | _, err = Orm.Use("naming").Exec("delete from judge where id=?", lastid) 159 | dangerous(err) 160 | } 161 | 162 | func dangerous(err error) { 163 | if err != nil { 164 | log.Fatalln(err) 165 | } 166 | } 167 | 168 | 169 | ``` -------------------------------------------------------------------------------- /log/console.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gogs Authors. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package log 6 | 7 | import ( 8 | "encoding/json" 9 | "fmt" 10 | "log" 11 | "os" 12 | "runtime" 13 | ) 14 | 15 | type Brush func(string) string 16 | 17 | func NewBrush(color string) Brush { 18 | pre := "\033[" 19 | reset := "\033[0m" 20 | return func(text string) string { 21 | return pre + color + "m" + text + reset 22 | } 23 | } 24 | 25 | var ( 26 | Red = NewBrush("1;31") 27 | Purple = NewBrush("1;35") 28 | Yellow = NewBrush("1;33") 29 | Green = NewBrush("1;32") 30 | Blue = NewBrush("1;34") 31 | Cyan = NewBrush("1;36") 32 | 33 | colors = []Brush{ 34 | Cyan, // Trace cyan 35 | Blue, // Debug blue 36 | Green, // Info green 37 | Yellow, // Warn yellow 38 | Red, // Error red 39 | Purple, // Critical purple 40 | Red, // Fatal red 41 | } 42 | consoleWriter = &ConsoleWriter{lg: log.New(os.Stdout, "", 0), 43 | Level: TRACE} 44 | ) 45 | 46 | // ConsoleWriter implements LoggerInterface and writes messages to terminal. 47 | type ConsoleWriter struct { 48 | lg *log.Logger 49 | Level int `json:"level"` 50 | Formatting bool `json:"formatting"` 51 | } 52 | 53 | // create ConsoleWriter returning as LoggerInterface. 54 | func NewConsole() LoggerInterface { 55 | return &ConsoleWriter{ 56 | lg: log.New(os.Stderr, "", log.Ldate|log.Ltime), 57 | Level: TRACE, 58 | Formatting: true, 59 | } 60 | } 61 | 62 | func (cw *ConsoleWriter) Init(config string) error { 63 | return json.Unmarshal([]byte(config), cw) 64 | } 65 | 66 | func (cw *ConsoleWriter) WriteMsg(msg string, skip, level int) error { 67 | if cw.Level > level { 68 | return nil 69 | } 70 | if runtime.GOOS == "windows" || !cw.Formatting { 71 | cw.lg.Println(msg) 72 | } else { 73 | cw.lg.Println(colors[level](msg)) 74 | } 75 | return nil 76 | } 77 | 78 | func (_ *ConsoleWriter) Flush() { 79 | 80 | } 81 | 82 | func (_ *ConsoleWriter) Destroy() { 83 | } 84 | 85 | func printConsole(level int, msg string) { 86 | consoleWriter.WriteMsg(msg, 0, level) 87 | } 88 | 89 | func printfConsole(level int, format string, v ...interface{}) { 90 | consoleWriter.WriteMsg(fmt.Sprintf(format, v...), 0, level) 91 | } 92 | 93 | // ConsoleTrace prints to stdout using TRACE colors 94 | func ConsoleTrace(s string) { 95 | printConsole(TRACE, s) 96 | } 97 | 98 | // ConsoleTracef prints a formatted string to stdout using TRACE colors 99 | func ConsoleTracef(format string, v ...interface{}) { 100 | printfConsole(TRACE, format, v...) 101 | } 102 | 103 | // ConsoleDebug prints to stdout using DEBUG colors 104 | func ConsoleDebug(s string) { 105 | printConsole(DEBUG, s) 106 | } 107 | 108 | // ConsoleDebugf prints a formatted string to stdout using DEBUG colors 109 | func ConsoleDebugf(format string, v ...interface{}) { 110 | printfConsole(DEBUG, format, v...) 111 | } 112 | 113 | // ConsoleInfo prints to stdout using INFO colors 114 | func ConsoleInfo(s string) { 115 | printConsole(INFO, s) 116 | } 117 | 118 | // ConsoleInfof prints a formatted string to stdout using INFO colors 119 | func ConsoleInfof(format string, v ...interface{}) { 120 | printfConsole(INFO, format, v...) 121 | } 122 | 123 | // ConsoleWarn prints to stdout using WARN colors 124 | func ConsoleWarn(s string) { 125 | printConsole(WARN, s) 126 | } 127 | 128 | // ConsoleWarnf prints a formatted string to stdout using WARN colors 129 | func ConsoleWarnf(format string, v ...interface{}) { 130 | printfConsole(WARN, format, v...) 131 | } 132 | 133 | // ConsoleError prints to stdout using ERROR colors 134 | func ConsoleError(s string) { 135 | printConsole(ERROR, s) 136 | } 137 | 138 | // ConsoleErrorf prints a formatted string to stdout using ERROR colors 139 | func ConsoleErrorf(format string, v ...interface{}) { 140 | printfConsole(ERROR, format, v...) 141 | } 142 | 143 | // ConsoleFatal prints to stdout using FATAL colors 144 | func ConsoleFatal(s string) { 145 | printConsole(FATAL, s) 146 | os.Exit(1) 147 | } 148 | 149 | // ConsoleFatalf prints a formatted string to stdout using FATAL colors 150 | func ConsoleFatalf(format string, v ...interface{}) { 151 | printfConsole(FATAL, format, v...) 152 | os.Exit(1) 153 | } 154 | 155 | func init() { 156 | Register("console", NewConsole) 157 | } 158 | -------------------------------------------------------------------------------- /log/file.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gogs Authors. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package log 6 | 7 | import ( 8 | "encoding/json" 9 | "errors" 10 | "fmt" 11 | "io/ioutil" 12 | "log" 13 | "os" 14 | "path/filepath" 15 | "strings" 16 | "sync" 17 | "time" 18 | ) 19 | 20 | // FileLogWriter implements LoggerInterface. 21 | // It writes messages by lines limit, file size limit, or time frequency. 22 | type FileLogWriter struct { 23 | *log.Logger 24 | mw *MuxWriter 25 | // The opened file 26 | Filename string `json:"filename"` 27 | 28 | Maxlines int `json:"maxlines"` 29 | maxlines_curlines int 30 | 31 | // Rotate at size 32 | Maxsize int `json:"maxsize"` 33 | maxsize_cursize int 34 | 35 | // Rotate daily 36 | Daily bool `json:"daily"` 37 | Maxdays int64 `json:"maxdays"` 38 | daily_opendate int 39 | 40 | Rotate bool `json:"rotate"` 41 | 42 | startLock sync.Mutex // Only one log can write to the file 43 | 44 | Level int `json:"level"` 45 | } 46 | 47 | // an *os.File writer with locker. 48 | type MuxWriter struct { 49 | sync.Mutex 50 | fd *os.File 51 | } 52 | 53 | // write to os.File. 54 | func (l *MuxWriter) Write(b []byte) (int, error) { 55 | l.Lock() 56 | defer l.Unlock() 57 | return l.fd.Write(b) 58 | } 59 | 60 | // set os.File in writer. 61 | func (l *MuxWriter) SetFd(fd *os.File) { 62 | if l.fd != nil { 63 | l.fd.Close() 64 | } 65 | l.fd = fd 66 | } 67 | 68 | // create a FileLogWriter returning as LoggerInterface. 69 | func NewFileWriter() LoggerInterface { 70 | w := &FileLogWriter{ 71 | Filename: "", 72 | Maxlines: 1000000, 73 | Maxsize: 1 << 28, //256 MB 74 | Daily: true, 75 | Maxdays: 7, 76 | Rotate: true, 77 | Level: TRACE, 78 | } 79 | // use MuxWriter instead direct use os.File for lock write when rotate 80 | w.mw = new(MuxWriter) 81 | // set MuxWriter as Logger's io.Writer 82 | w.Logger = log.New(w.mw, "", log.Ldate|log.Ltime) 83 | return w 84 | } 85 | 86 | // Init file logger with json config. 87 | // config like: 88 | // { 89 | // "filename":"log/gogs.log", 90 | // "maxlines":10000, 91 | // "maxsize":1<<30, 92 | // "daily":true, 93 | // "maxdays":15, 94 | // "rotate":true 95 | // } 96 | func (w *FileLogWriter) Init(config string) error { 97 | if err := json.Unmarshal([]byte(config), w); err != nil { 98 | return err 99 | } 100 | if len(w.Filename) == 0 { 101 | return errors.New("config must have filename") 102 | } 103 | return w.StartLogger() 104 | } 105 | 106 | // start file logger. create log file and set to locker-inside file writer. 107 | func (w *FileLogWriter) StartLogger() error { 108 | fd, err := w.createLogFile() 109 | if err != nil { 110 | return err 111 | } 112 | w.mw.SetFd(fd) 113 | if err = w.initFd(); err != nil { 114 | return err 115 | } 116 | return nil 117 | } 118 | 119 | func (w *FileLogWriter) docheck(size int) { 120 | w.startLock.Lock() 121 | defer w.startLock.Unlock() 122 | if w.Rotate && ((w.Maxlines > 0 && w.maxlines_curlines >= w.Maxlines) || 123 | (w.Maxsize > 0 && w.maxsize_cursize >= w.Maxsize) || 124 | (w.Daily && time.Now().Day() != w.daily_opendate)) { 125 | if err := w.DoRotate(); err != nil { 126 | fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) 127 | return 128 | } 129 | } 130 | w.maxlines_curlines++ 131 | w.maxsize_cursize += size 132 | } 133 | 134 | // write logger message into file. 135 | func (w *FileLogWriter) WriteMsg(msg string, skip, level int) error { 136 | if level < w.Level { 137 | return nil 138 | } 139 | n := 24 + len(msg) // 24 stand for the length "2013/06/23 21:00:22 [T] " 140 | w.docheck(n) 141 | w.Logger.Println(msg) 142 | return nil 143 | } 144 | 145 | func (w *FileLogWriter) createLogFile() (*os.File, error) { 146 | // Open the log file 147 | return os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) 148 | } 149 | 150 | func (w *FileLogWriter) initFd() error { 151 | fd := w.mw.fd 152 | finfo, err := fd.Stat() 153 | if err != nil { 154 | return fmt.Errorf("get stat: %s\n", err) 155 | } 156 | w.maxsize_cursize = int(finfo.Size()) 157 | w.daily_opendate = time.Now().Day() 158 | if finfo.Size() > 0 { 159 | content, err := ioutil.ReadFile(w.Filename) 160 | if err != nil { 161 | return err 162 | } 163 | w.maxlines_curlines = len(strings.Split(string(content), "\n")) 164 | } else { 165 | w.maxlines_curlines = 0 166 | } 167 | return nil 168 | } 169 | 170 | // DoRotate means it need to write file in new file. 171 | // new file name like xx.log.2013-01-01.2 172 | func (w *FileLogWriter) DoRotate() error { 173 | _, err := os.Lstat(w.Filename) 174 | if err == nil { // file exists 175 | // Find the next available number 176 | num := 1 177 | fname := "" 178 | for ; err == nil && num <= 999; num++ { 179 | fname = w.Filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), num) 180 | _, err = os.Lstat(fname) 181 | } 182 | // return error if the last file checked still existed 183 | if err == nil { 184 | return fmt.Errorf("rotate: cannot find free log number to rename %s\n", w.Filename) 185 | } 186 | 187 | // block Logger's io.Writer 188 | w.mw.Lock() 189 | defer w.mw.Unlock() 190 | 191 | fd := w.mw.fd 192 | fd.Close() 193 | 194 | // close fd before rename 195 | // Rename the file to its newfound home 196 | if err = os.Rename(w.Filename, fname); err != nil { 197 | return fmt.Errorf("Rotate: %s\n", err) 198 | } 199 | 200 | // re-start logger 201 | if err = w.StartLogger(); err != nil { 202 | return fmt.Errorf("Rotate StartLogger: %s\n", err) 203 | } 204 | 205 | go w.deleteOldLog() 206 | } 207 | 208 | return nil 209 | } 210 | 211 | func (w *FileLogWriter) deleteOldLog() { 212 | dir := filepath.Dir(w.Filename) 213 | filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) { 214 | defer func() { 215 | if r := recover(); r != nil { 216 | returnErr = fmt.Errorf("Unable to delete old log '%s', error: %+v", path, r) 217 | } 218 | }() 219 | 220 | if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.Maxdays) { 221 | if strings.HasPrefix(filepath.Base(path), filepath.Base(w.Filename)) { 222 | os.Remove(path) 223 | } 224 | } 225 | return returnErr 226 | }) 227 | } 228 | 229 | // destroy file logger, close file writer. 230 | func (w *FileLogWriter) Destroy() { 231 | w.mw.fd.Close() 232 | } 233 | 234 | // flush file logger. 235 | // there are no buffering messages in file logger in memory. 236 | // flush file means sync file from disk. 237 | func (w *FileLogWriter) Flush() { 238 | w.mw.fd.Sync() 239 | } 240 | 241 | func init() { 242 | Register("file", NewFileWriter) 243 | } 244 | -------------------------------------------------------------------------------- /log/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gogs Authors. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package log 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "path/filepath" 11 | "runtime" 12 | "strings" 13 | "sync" 14 | ) 15 | 16 | const skip int = 3 17 | 18 | var ( 19 | loggers []*Logger 20 | ) 21 | 22 | func NewLogger(bufLen int64, mode, config string) { 23 | logger := newLogger(bufLen) 24 | 25 | isExist := false 26 | for _, l := range loggers { 27 | if l.adapter == mode { 28 | isExist = true 29 | l = logger 30 | } 31 | } 32 | if !isExist { 33 | loggers = append(loggers, logger) 34 | } 35 | if err := logger.SetLogger(mode, config); err != nil { 36 | Fatal("Fail to set logger(%s): %v", mode, err) 37 | } 38 | } 39 | 40 | func GetLogger(bufLen int64, mode, config string) *Logger { 41 | for _, l := range loggers { 42 | if l.adapter == mode { 43 | return l 44 | } 45 | } 46 | 47 | logger := newLogger(bufLen) 48 | loggers = append(loggers, logger) 49 | if err := logger.SetLogger(mode, config); err != nil { 50 | Fatal("Fail to set logger(%s): %v", mode, err) 51 | } 52 | 53 | return logger 54 | } 55 | 56 | func Trace(format string, v ...interface{}) { 57 | for _, logger := range loggers { 58 | logger.Trace(format, v...) 59 | } 60 | } 61 | 62 | func Debug(format string, v ...interface{}) { 63 | for _, logger := range loggers { 64 | logger.Debug(format, v...) 65 | } 66 | } 67 | 68 | func Info(format string, v ...interface{}) { 69 | for _, logger := range loggers { 70 | logger.Info(format, v...) 71 | } 72 | } 73 | 74 | func Warn(format string, v ...interface{}) { 75 | for _, logger := range loggers { 76 | logger.Warn(format, v...) 77 | } 78 | } 79 | 80 | func Error(format string, v ...interface{}) { 81 | for _, logger := range loggers { 82 | logger.Error(skip, format, v...) 83 | } 84 | } 85 | 86 | func Critical(format string, v ...interface{}) { 87 | for _, logger := range loggers { 88 | logger.Critical(skip, format, v...) 89 | } 90 | } 91 | 92 | func Fatal(format string, v ...interface{}) { 93 | for _, l := range loggers { 94 | l.Error(skip, format, v...) 95 | l.Close() 96 | } 97 | os.Exit(1) 98 | } 99 | 100 | func Close() { 101 | for _, l := range loggers { 102 | l.Close() 103 | // delete the logger. 104 | l = nil 105 | } 106 | // clear the loggers slice. 107 | loggers = nil 108 | } 109 | 110 | func WriteLog(loginfo string) { 111 | _, file, line, ok := runtime.Caller(1) 112 | if ok && len(loginfo) > 0 { 113 | Info("file:%s,line:%d,info:%s", file, line, loginfo) 114 | } 115 | 116 | } 117 | 118 | // .___ __ _____ 119 | // | | _____/ |_ ____________/ ____\____ ____ ____ 120 | // | |/ \ __\/ __ \_ __ \ __\\__ \ _/ ___\/ __ \ 121 | // | | | \ | \ ___/| | \/| | / __ \\ \__\ ___/ 122 | // |___|___| /__| \___ >__| |__| (____ /\___ >___ > 123 | // \/ \/ \/ \/ \/ 124 | 125 | type LogLevel int 126 | 127 | const ( 128 | TRACE = iota 129 | DEBUG 130 | INFO 131 | WARN 132 | ERROR 133 | CRITICAL 134 | FATAL 135 | ) 136 | 137 | // LoggerInterface represents behaviors of a logger provider. 138 | type LoggerInterface interface { 139 | Init(config string) error 140 | WriteMsg(msg string, skip, level int) error 141 | Destroy() 142 | Flush() 143 | } 144 | 145 | type loggerType func() LoggerInterface 146 | 147 | var adapters = make(map[string]loggerType) 148 | 149 | // Register registers given logger provider to adapters. 150 | func Register(name string, log loggerType) { 151 | if log == nil { 152 | panic("log: register provider is nil") 153 | } 154 | if _, dup := adapters[name]; dup { 155 | panic("log: register called twice for provider \"" + name + "\"") 156 | } 157 | adapters[name] = log 158 | } 159 | 160 | type logMsg struct { 161 | skip, level int 162 | msg string 163 | } 164 | 165 | // Logger is default logger in beego application. 166 | // it can contain several providers and log message into all providers. 167 | type Logger struct { 168 | adapter string 169 | lock sync.Mutex 170 | level int 171 | msg chan *logMsg 172 | outputs map[string]LoggerInterface 173 | quit chan bool 174 | } 175 | 176 | // newLogger initializes and returns a new logger. 177 | func newLogger(buffer int64) *Logger { 178 | l := &Logger{ 179 | msg: make(chan *logMsg, buffer), 180 | outputs: make(map[string]LoggerInterface), 181 | quit: make(chan bool), 182 | } 183 | go l.StartLogger() 184 | return l 185 | } 186 | 187 | // SetLogger sets new logger instanse with given logger adapter and config. 188 | func (l *Logger) SetLogger(adapter string, config string) error { 189 | l.lock.Lock() 190 | defer l.lock.Unlock() 191 | if log, ok := adapters[adapter]; ok { 192 | lg := log() 193 | if err := lg.Init(config); err != nil { 194 | return err 195 | } 196 | l.outputs[adapter] = lg 197 | l.adapter = adapter 198 | } else { 199 | panic("log: unknown adapter \"" + adapter + "\" (forgotten register?)") 200 | } 201 | return nil 202 | } 203 | 204 | // DelLogger removes a logger adapter instance. 205 | func (l *Logger) DelLogger(adapter string) error { 206 | l.lock.Lock() 207 | defer l.lock.Unlock() 208 | if lg, ok := l.outputs[adapter]; ok { 209 | lg.Destroy() 210 | delete(l.outputs, adapter) 211 | } else { 212 | panic("log: unknown adapter \"" + adapter + "\" (forgotten register?)") 213 | } 214 | return nil 215 | } 216 | 217 | func (l *Logger) writerMsg(skip, level int, msg string) error { 218 | if l.level > level { 219 | return nil 220 | } 221 | lm := &logMsg{ 222 | skip: skip, 223 | level: level, 224 | } 225 | 226 | // Only error information needs locate position for debugging. 227 | if lm.level >= ERROR { 228 | pc, file, line, ok := runtime.Caller(skip) 229 | if ok { 230 | // Get caller function name. 231 | fn := runtime.FuncForPC(pc) 232 | var fnName string 233 | if fn == nil { 234 | fnName = "?()" 235 | } else { 236 | fnName = strings.TrimLeft(filepath.Ext(fn.Name()), ".") + "()" 237 | } 238 | 239 | lm.msg = fmt.Sprintf("[%s:%d %s] %s", filepath.Base(file), line, fnName, msg) 240 | } else { 241 | lm.msg = msg 242 | } 243 | } else { 244 | lm.msg = msg 245 | } 246 | l.msg <- lm 247 | return nil 248 | } 249 | 250 | // StartLogger starts logger chan reading. 251 | func (l *Logger) StartLogger() { 252 | for { 253 | select { 254 | case bm := <-l.msg: 255 | for _, l := range l.outputs { 256 | if err := l.WriteMsg(bm.msg, bm.skip, bm.level); err != nil { 257 | fmt.Println("ERROR, unable to WriteMsg:", err) 258 | } 259 | } 260 | case <-l.quit: 261 | return 262 | } 263 | } 264 | } 265 | 266 | // Flush flushs all chan data. 267 | func (l *Logger) Flush() { 268 | for _, l := range l.outputs { 269 | l.Flush() 270 | } 271 | } 272 | 273 | // Close closes logger, flush all chan data and destroy all adapter instances. 274 | func (l *Logger) Close() { 275 | l.quit <- true 276 | for { 277 | if len(l.msg) > 0 { 278 | bm := <-l.msg 279 | for _, l := range l.outputs { 280 | if err := l.WriteMsg(bm.msg, bm.skip, bm.level); err != nil { 281 | fmt.Println("ERROR, unable to WriteMsg:", err) 282 | } 283 | } 284 | } else { 285 | break 286 | } 287 | } 288 | for _, l := range l.outputs { 289 | l.Flush() 290 | l.Destroy() 291 | } 292 | } 293 | 294 | func (l *Logger) Trace(format string, v ...interface{}) { 295 | msg := fmt.Sprintf("[T] "+format, v...) 296 | l.writerMsg(0, TRACE, msg) 297 | } 298 | 299 | func (l *Logger) Debug(format string, v ...interface{}) { 300 | msg := fmt.Sprintf("[D] "+format, v...) 301 | l.writerMsg(0, DEBUG, msg) 302 | } 303 | 304 | func (l *Logger) Info(format string, v ...interface{}) { 305 | msg := fmt.Sprintf("[I] "+format, v...) 306 | l.writerMsg(0, INFO, msg) 307 | } 308 | 309 | func (l *Logger) Warn(format string, v ...interface{}) { 310 | msg := fmt.Sprintf("[W] "+format, v...) 311 | l.writerMsg(0, WARN, msg) 312 | } 313 | 314 | func (l *Logger) Error(skip int, format string, v ...interface{}) { 315 | msg := fmt.Sprintf("[E] "+format, v...) 316 | l.writerMsg(skip, ERROR, msg) 317 | } 318 | 319 | func (l *Logger) Critical(skip int, format string, v ...interface{}) { 320 | msg := fmt.Sprintf("[C] "+format, v...) 321 | l.writerMsg(skip, CRITICAL, msg) 322 | } 323 | 324 | func (l *Logger) Fatal(skip int, format string, v ...interface{}) { 325 | msg := fmt.Sprintf("[F] "+format, v...) 326 | l.writerMsg(skip, FATAL, msg) 327 | l.Close() 328 | os.Exit(1) 329 | } 330 | -------------------------------------------------------------------------------- /orm/repo.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "bytes" 5 | "database/sql" 6 | "fmt" 7 | "log" 8 | "reflect" 9 | "strconv" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | // Repo 封装一次查询 15 | type Repo struct { 16 | o *Orm 17 | tbl string 18 | db *sql.DB 19 | where string 20 | args []interface{} 21 | orderBy string 22 | limit int 23 | offset int 24 | showSQL bool 25 | cols string 26 | sql string 27 | } 28 | 29 | // Use 使用哪个数据库实例 30 | func (r *Repo) Use(name string) *Repo { 31 | r.db = r.o.Use(name) 32 | return r 33 | } 34 | 35 | // Where 查询条件 36 | func (r *Repo) Where(where string, args ...interface{}) *Repo { 37 | r.where = where 38 | r.args = args 39 | return r 40 | } 41 | 42 | // OrderBy e.g. name => order by name 43 | func (r *Repo) OrderBy(by string) *Repo { 44 | r.orderBy = by 45 | return r 46 | } 47 | 48 | // Limit 设置limit和offset 49 | func (r *Repo) Limit(limit int, offset ...int) *Repo { 50 | r.limit = limit 51 | if len(offset) > 0 { 52 | r.offset = offset[0] 53 | } 54 | return r 55 | } 56 | 57 | // Quiet 不传参数则设置showSQL为false 58 | func (r *Repo) Quiet(showSQL ...bool) *Repo { 59 | if len(showSQL) > 0 { 60 | r.showSQL = showSQL[0] 61 | } else { 62 | r.showSQL = false 63 | } 64 | return r 65 | } 66 | 67 | // Cols 设置要查询的column 68 | func (r *Repo) Cols(cols string) *Repo { 69 | r.cols = cols 70 | return r 71 | } 72 | 73 | func (r *Repo) insure() { 74 | if r.db == nil { 75 | r.Use("default") 76 | } 77 | } 78 | 79 | func (r *Repo) p(query string, args []interface{}) { 80 | if r.showSQL { 81 | log.Println("[orm]", query, "params:", args) 82 | } 83 | } 84 | 85 | func (r *Repo) exec(query string, args ...interface{}) (sql.Result, error) { 86 | r.insure() 87 | r.p(query, args) 88 | return r.db.Exec(query, args...) 89 | } 90 | 91 | func (r *Repo) queryRow(query string, args ...interface{}) *sql.Row { 92 | r.insure() 93 | r.p(query, args) 94 | return r.db.QueryRow(query, args...) 95 | } 96 | 97 | func (r *Repo) query(query string, args ...interface{}) (*sql.Rows, error) { 98 | r.insure() 99 | r.p(query, args) 100 | return r.db.Query(query, args...) 101 | } 102 | 103 | func (r *Repo) buildSQL() { 104 | if r.cols == "" { 105 | r.cols = "*" 106 | } 107 | 108 | buf := new(bytes.Buffer) 109 | buf.WriteString("SELECT ") 110 | buf.WriteString(r.cols) 111 | buf.WriteString(" FROM `") 112 | buf.WriteString(r.tbl) 113 | buf.WriteString("`") 114 | 115 | if r.where != "" { 116 | buf.WriteString(" WHERE ") 117 | buf.WriteString(r.where) 118 | } 119 | 120 | if r.orderBy != "" { 121 | buf.WriteString(" ORDER BY ") 122 | buf.WriteString(r.orderBy) 123 | } 124 | 125 | if r.limit > 0 { 126 | buf.WriteString(" LIMIT ?") 127 | r.args = append(r.args, r.limit) 128 | } 129 | 130 | if r.offset > 0 { 131 | buf.WriteString(" OFFSET ?") 132 | r.args = append(r.args, r.offset) 133 | } 134 | 135 | r.sql = buf.String() 136 | } 137 | 138 | // Count 统计数目 139 | func (r *Repo) Count() (count int, err error) { 140 | r.cols = "count(*) as count" 141 | r.buildSQL() 142 | err = r.queryRow(r.sql, r.args...).Scan(&count) 143 | return 144 | } 145 | 146 | // Insert 保存一条数据,返回lastid 147 | func (r *Repo) Insert(attrs G) (sql.Result, error) { 148 | ln := len(attrs) 149 | keys := make([]string, 0, ln) 150 | qms := make([]string, 0, ln) 151 | vals := make([]interface{}, 0, ln) 152 | for k, v := range attrs { 153 | keys = append(keys, fmt.Sprintf("`%s`", k)) 154 | qms = append(qms, "?") 155 | vals = append(vals, v) 156 | } 157 | 158 | s := fmt.Sprintf( 159 | "INSERT INTO `%s`(%s) VALUES(%s)", 160 | r.tbl, 161 | strings.Join(keys, ","), 162 | strings.Join(qms, ","), 163 | ) 164 | 165 | return r.exec(s, vals...) 166 | } 167 | 168 | // Delete 根据where条件做删除,返回被影响的行数 169 | func (r *Repo) Delete() (int64, error) { 170 | s := fmt.Sprintf("DELETE FROM `%s`", r.tbl) 171 | if r.where != "" { 172 | s += " WHERE " + r.where 173 | } 174 | 175 | if r.limit > 0 { 176 | s += " LIMIT ?" 177 | r.args = append(r.args, r.limit) 178 | } 179 | 180 | ret, err := r.exec(s, r.args...) 181 | if err != nil { 182 | return 0, err 183 | } 184 | 185 | return ret.RowsAffected() 186 | } 187 | 188 | // Update 更新记录 189 | func (r *Repo) Update(attrs G) (int64, error) { 190 | ln := len(attrs) 191 | keys := make([]string, 0, ln) 192 | vals := make([]interface{}, 0, ln) 193 | for k, v := range attrs { 194 | keys = append(keys, fmt.Sprintf("`%s`=?", k)) 195 | vals = append(vals, v) 196 | } 197 | 198 | s := fmt.Sprintf("UPDATE `%s` SET %s", r.tbl, strings.Join(keys, ",")) 199 | if r.where != "" { 200 | s += " WHERE " + r.where 201 | vals = append(vals, r.args...) 202 | } 203 | 204 | if r.limit > 0 { 205 | vals = append(vals, r.limit) 206 | } 207 | 208 | ret, err := r.exec(s, vals...) 209 | if err != nil { 210 | return 0, err 211 | } 212 | 213 | return ret.RowsAffected() 214 | } 215 | 216 | // I64Col 获取一列数据,数据类型是int64 217 | func (r *Repo) I64Col(col string) ([]int64, error) { 218 | cols := []int64{} 219 | rs, err := r.col(col) 220 | if err != nil { 221 | return cols, err 222 | } 223 | 224 | defer rs.Close() 225 | 226 | for rs.Next() { 227 | var item int64 228 | err = rs.Scan(&item) 229 | if err != nil { 230 | return cols, err 231 | } 232 | 233 | cols = append(cols, item) 234 | } 235 | 236 | return cols, err 237 | } 238 | 239 | // StrCol 获取一列数据,数据类型是string 240 | func (r *Repo) StrCol(col string) ([]string, error) { 241 | cols := []string{} 242 | rs, err := r.col(col) 243 | if err != nil { 244 | return cols, err 245 | } 246 | 247 | defer rs.Close() 248 | 249 | for rs.Next() { 250 | var item string 251 | err = rs.Scan(&item) 252 | if err != nil { 253 | return cols, err 254 | } 255 | 256 | cols = append(cols, item) 257 | } 258 | 259 | return cols, err 260 | } 261 | 262 | func (r *Repo) col(col string) (*sql.Rows, error) { 263 | r.cols = col 264 | r.buildSQL() 265 | return r.query(r.sql, r.args...) 266 | } 267 | 268 | // U64s 将uint64类型的slice拼接成逗号分隔的string 269 | func U64s(ids []uint64) string { 270 | count := len(ids) 271 | strs := make([]string, count) 272 | for i := 0; i < count; i++ { 273 | strs[i] = fmt.Sprint(ids[i]) 274 | } 275 | return strings.Join(strs, ",") 276 | } 277 | 278 | // I64s 将int64类型的slice拼接成逗号分隔的string 279 | func I64s(ids []int64) string { 280 | count := len(ids) 281 | strs := make([]string, count) 282 | for i := 0; i < count; i++ { 283 | strs[i] = fmt.Sprint(ids[i]) 284 | } 285 | return strings.Join(strs, ",") 286 | } 287 | 288 | // I64Arr 将逗号分隔的字符串ID转换成[]int64 289 | func I64Arr(ids string) []int64 { 290 | if ids == "" { 291 | return []int64{} 292 | } 293 | 294 | arr := strings.Split(ids, ",") 295 | count := len(arr) 296 | ret := make([]int64, 0, count) 297 | for i := 0; i < count; i++ { 298 | if arr[i] == "" { 299 | continue 300 | } 301 | id, err := strconv.ParseInt(arr[i], 10, 64) 302 | if err != nil { 303 | continue 304 | } 305 | ret = append(ret, id) 306 | } 307 | return ret 308 | } 309 | 310 | // Rows 查询多行记录 311 | func (r *Repo) Rows() (*sql.Rows, error) { 312 | r.insure() 313 | r.buildSQL() 314 | r.p(r.sql, r.args) 315 | stmt, err := r.db.Prepare(r.sql) 316 | if err != nil { 317 | return nil, err 318 | } 319 | 320 | defer stmt.Close() 321 | return stmt.Query(r.args...) 322 | } 323 | 324 | // Row 查询一条记录 325 | func (r *Repo) Row() *sql.Row { 326 | r.buildSQL() 327 | return r.queryRow(r.sql, r.args...) 328 | } 329 | 330 | // Find 查找一个struct,传入的第一个参数是struct的指针 331 | func (r *Repo) Find(ptr interface{}) (bool, error) { 332 | rows, err := r.Rows() 333 | if err != nil { 334 | return false, err 335 | } 336 | 337 | val := reflect.ValueOf(ptr) 338 | 339 | defer rows.Close() 340 | 341 | if rows.Next() { 342 | err = r.scanRows(val, rows) 343 | if err != nil { 344 | return false, err 345 | } 346 | } else { 347 | return false, nil 348 | } 349 | 350 | return true, nil 351 | } 352 | 353 | // Finds 查询一个列表,ptr e.g. var user []*User -> &user 354 | func (r *Repo) Finds(ptr interface{}) error { 355 | rows, err := r.Rows() 356 | if err != nil { 357 | return err 358 | } 359 | 360 | sliceValue := reflect.Indirect(reflect.ValueOf(ptr)) 361 | structType := sliceValue.Type().Elem().Elem() 362 | 363 | defer rows.Close() 364 | 365 | for rows.Next() { 366 | rowValue := reflect.New(structType) 367 | err = r.scanRows(rowValue, rows) 368 | if err != nil { 369 | return err 370 | } 371 | sliceValue.Set(reflect.Append(sliceValue, rowValue)) 372 | } 373 | 374 | return nil 375 | } 376 | 377 | func (r *Repo) scanRows(val reflect.Value, rows *sql.Rows) (err error) { 378 | cols, _ := rows.Columns() 379 | 380 | containers := make([]interface{}, 0, len(cols)) 381 | for i := 0; i < cap(containers); i++ { 382 | var v interface{} 383 | containers = append(containers, &v) 384 | } 385 | 386 | err = rows.Scan(containers...) 387 | if err != nil { 388 | return 389 | } 390 | 391 | typ := val.Type() 392 | 393 | for i, v := range containers { 394 | value := reflect.Indirect(reflect.ValueOf(v)) 395 | if !value.Elem().IsValid() { 396 | continue 397 | } 398 | 399 | key := cols[i] 400 | 401 | field := val.Elem().FieldByName(r.o.Tag2field(typ, key)) 402 | if field.IsValid() { 403 | // value -> field 404 | err = setModelValue(value, field) 405 | if err != nil { 406 | return 407 | } 408 | } 409 | } 410 | 411 | return 412 | } 413 | 414 | func parseBool(value reflect.Value) bool { 415 | return value.Bool() 416 | } 417 | 418 | func setPtrValue(driverValue, fieldValue reflect.Value) { 419 | t := fieldValue.Type().Elem() 420 | v := reflect.New(t) 421 | fieldValue.Set(v) 422 | switch t.Kind() { 423 | case reflect.String: 424 | v.Elem().SetString(string(driverValue.Interface().([]uint8))) 425 | case reflect.Int64: 426 | v.Elem().SetInt(driverValue.Interface().(int64)) 427 | case reflect.Float64: 428 | v.Elem().SetFloat(driverValue.Interface().(float64)) 429 | case reflect.Bool: 430 | v.Elem().SetBool(driverValue.Interface().(bool)) 431 | } 432 | } 433 | 434 | func setModelValue(driverValue, fieldValue reflect.Value) error { 435 | switch fieldValue.Type().Kind() { 436 | case reflect.Bool: 437 | fieldValue.SetBool(parseBool(driverValue.Elem())) 438 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 439 | fieldValue.SetInt(driverValue.Elem().Int()) 440 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 441 | // reading uint from int value causes panic 442 | switch driverValue.Elem().Kind() { 443 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 444 | fieldValue.SetUint(uint64(driverValue.Elem().Int())) 445 | default: 446 | fieldValue.SetUint(driverValue.Elem().Uint()) 447 | } 448 | case reflect.Float32, reflect.Float64: 449 | fieldValue.SetFloat(driverValue.Elem().Float()) 450 | case reflect.String: 451 | fieldValue.SetString(string(driverValue.Elem().Bytes())) 452 | case reflect.Slice: 453 | if reflect.TypeOf(driverValue.Interface()).Elem().Kind() == reflect.Uint8 { 454 | fieldValue.SetBytes(driverValue.Elem().Bytes()) 455 | } 456 | case reflect.Ptr: 457 | setPtrValue(driverValue, fieldValue) 458 | case reflect.Struct: 459 | switch fieldValue.Interface().(type) { 460 | case time.Time: 461 | fieldValue.Set(driverValue.Elem()) 462 | default: 463 | if scanner, ok := fieldValue.Addr().Interface().(sql.Scanner); ok { 464 | return scanner.Scan(driverValue.Interface()) 465 | } 466 | } 467 | } 468 | return nil 469 | } 470 | --------------------------------------------------------------------------------