Browse Source

提交库

lxg 2 years ago
commit
f6f0c98b42
16 changed files with 1019 additions and 0 deletions
  1. 111 0
      db/gorm-logger.go
  2. 44 0
      db/sqlite.go
  3. 72 0
      utils/copy.go
  4. 111 0
      utils/gorm-logger.go
  5. 22 0
      utils/http.go
  6. 26 0
      utils/log_debug.go
  7. 19 0
      utils/log_linux.go
  8. 43 0
      utils/log_release.go
  9. 37 0
      utils/log_windows.go
  10. 143 0
      utils/paging.go
  11. 18 0
      utils/statik-file-system.go
  12. 38 0
      utils/string.go
  13. 69 0
      utils/time.go
  14. 256 0
      utils/utils.go
  15. 5 0
      utils/utils_debug.go
  16. 5 0
      utils/utils_release.go

+ 111 - 0
db/gorm-logger.go

@@ -0,0 +1,111 @@
+package db
+
+import (
+	"database/sql/driver"
+	"fmt"
+	"log"
+	"os"
+	"reflect"
+	"regexp"
+	"strconv"
+	"time"
+	"unicode"
+)
+
+var (
+	DefaultGormLogger        = GormLogger{log.New(os.Stdout, "[ORM] ", 0)}
+	sqlRegexp                = regexp.MustCompile(`\?`)
+	numericPlaceHolderRegexp = regexp.MustCompile(`\$\d+`)
+)
+
+func isPrintable(s string) bool {
+	for _, r := range s {
+		if !unicode.IsPrint(r) {
+			return false
+		}
+	}
+	return true
+}
+
+var LogFormatter = func(values ...interface{}) (messages []interface{}) {
+	if len(values) > 1 {
+		var (
+			sql             string
+			formattedValues []string
+			level           = values[0]
+			currentTime     = time.Now().Format("2006/01/02 15:04:05")
+			// source          = fmt.Sprintf(" %v", values[1])
+		)
+
+		messages = []interface{}{currentTime}
+
+		if level == "sql" {
+			// duration
+			messages = append(messages, fmt.Sprintf("[%.2fms]", float64(values[2].(time.Duration).Nanoseconds()/1e4)/100.0))
+			// sql
+			for _, value := range values[4].([]interface{}) {
+				indirectValue := reflect.Indirect(reflect.ValueOf(value))
+				if indirectValue.IsValid() {
+					value = indirectValue.Interface()
+					if t, ok := value.(time.Time); ok {
+						formattedValues = append(formattedValues, fmt.Sprintf("'%v'", t.Format("2006-01-02 15:04:05")))
+					} else if b, ok := value.([]byte); ok {
+						if str := string(b); isPrintable(str) {
+							formattedValues = append(formattedValues, fmt.Sprintf("'%v'", str))
+						} else {
+							formattedValues = append(formattedValues, "'<binary>'")
+						}
+					} else if r, ok := value.(driver.Valuer); ok {
+						if value, err := r.Value(); err == nil && value != nil {
+							formattedValues = append(formattedValues, fmt.Sprintf("'%v'", value))
+						} else {
+							formattedValues = append(formattedValues, "NULL")
+						}
+					} else {
+						formattedValues = append(formattedValues, fmt.Sprintf("'%v'", value))
+					}
+				} else {
+					formattedValues = append(formattedValues, "NULL")
+				}
+			}
+
+			// differentiate between $n placeholders or else treat like ?
+			if numericPlaceHolderRegexp.MatchString(values[3].(string)) {
+				sql = values[3].(string)
+				for index, value := range formattedValues {
+					placeholder := fmt.Sprintf(`\$%d([^\d]|$)`, index+1)
+					sql = regexp.MustCompile(placeholder).ReplaceAllString(sql, value+"$1")
+				}
+			} else {
+				formattedValuesLength := len(formattedValues)
+				for index, value := range sqlRegexp.Split(values[3].(string), -1) {
+					sql += value
+					if index < formattedValuesLength {
+						sql += formattedValues[index]
+					}
+				}
+			}
+
+			messages = append(messages, sql)
+			messages = append(messages, fmt.Sprintf("\r\n%v", strconv.FormatInt(values[5].(int64), 10)+" rows affected or returned"))
+		} else {
+			messages = append(messages, values[2:]...)
+		}
+	}
+
+	return
+}
+
+type logger interface {
+	Print(v ...interface{})
+}
+
+// Logger default logger
+type GormLogger struct {
+	*log.Logger
+}
+
+// Print format & print log
+func (logger GormLogger) Print(values ...interface{}) {
+	logger.Println(LogFormatter(values...)...)
+}

+ 44 - 0
db/sqlite.go

@@ -0,0 +1,44 @@
+package db
+
+import (
+	"fmt"
+	"log"
+
+	"github.com/jinzhu/gorm"
+	_ "github.com/jinzhu/gorm/dialects/sqlite"
+	"github.com/penggy/EasyGoLib/utils"
+)
+
+type Model struct {
+	ID        string         `structs:"id" gorm:"primary_key" form:"id" json:"id"`
+	CreatedAt utils.DateTime `structs:"-" json:"createdAt" gorm:"type:datetime"`
+	UpdatedAt utils.DateTime `structs:"-" json:"updatedAt" gorm:"type:datetime"`
+	// DeletedAt *time.Time `sql:"index" structs:"-"`
+}
+
+var SQLite *gorm.DB
+
+func Init() (err error) {
+	gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTablename string) string {
+		return "t_" + defaultTablename
+	}
+	dbFile := utils.DBFile()
+	log.Println("db file -->", utils.DBFile())
+	SQLite, err = gorm.Open("sqlite3", fmt.Sprintf("%s?loc=Asia/Shanghai", dbFile))
+	if err != nil {
+		return
+	}
+	// Sqlite cannot handle concurrent writes, so we limit sqlite to one connection.
+	// see https://github.com/mattn/go-sqlite3/issues/274
+	SQLite.DB().SetMaxOpenConns(1)
+	SQLite.SetLogger(DefaultGormLogger)
+	SQLite.LogMode(false)
+	return
+}
+
+func Close() {
+	if SQLite != nil {
+		SQLite.Close()
+		SQLite = nil
+	}
+}

+ 72 - 0
utils/copy.go

@@ -0,0 +1,72 @@
+package utils
+
+import (
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+)
+
+// Copy copies src to dest, doesn't matter if src is a directory or a file
+func Copy(src, dest string) error {
+	src = ExpandHomeDir(filepath.Clean(src))
+	dest = ExpandHomeDir(filepath.Clean(dest))
+
+	info, err := os.Stat(src)
+	if err != nil {
+		return err
+	}
+	return copy(src, dest, info)
+}
+
+// "info" must be given here, NOT nil.
+func copy(src, dest string, info os.FileInfo) error {
+	if info.IsDir() {
+		return dcopy(src, dest, info)
+	}
+	return fcopy(src, dest, info)
+}
+
+func fcopy(src, dest string, info os.FileInfo) error {
+	f, err := os.Create(dest)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	if err = os.Chmod(f.Name(), info.Mode()); err != nil {
+		return err
+	}
+
+	s, err := os.Open(src)
+	if err != nil {
+		return err
+	}
+	defer s.Close()
+
+	_, err = io.Copy(f, s)
+	return err
+}
+
+func dcopy(src, dest string, info os.FileInfo) error {
+	if err := os.MkdirAll(dest, info.Mode()); err != nil {
+		return err
+	}
+
+	infos, err := ioutil.ReadDir(src)
+	if err != nil {
+		return err
+	}
+
+	for _, info := range infos {
+		if err := copy(
+			filepath.Join(src, info.Name()),
+			filepath.Join(dest, info.Name()),
+			info,
+		); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}

+ 111 - 0
utils/gorm-logger.go

@@ -0,0 +1,111 @@
+package utils
+
+import (
+	"database/sql/driver"
+	"fmt"
+	"log"
+	"os"
+	"reflect"
+	"regexp"
+	"strconv"
+	"time"
+	"unicode"
+)
+
+var (
+	DefaultGormLogger        = GormLogger{log.New(os.Stdout, "[ORM] ", 0)}
+	sqlRegexp                = regexp.MustCompile(`\?`)
+	numericPlaceHolderRegexp = regexp.MustCompile(`\$\d+`)
+)
+
+func isPrintable(s string) bool {
+	for _, r := range s {
+		if !unicode.IsPrint(r) {
+			return false
+		}
+	}
+	return true
+}
+
+var LogFormatter = func(values ...interface{}) (messages []interface{}) {
+	if len(values) > 1 {
+		var (
+			sql             string
+			formattedValues []string
+			level           = values[0]
+			currentTime     = time.Now().Format("2006/01/02 15:04:05")
+			// source          = fmt.Sprintf(" %v", values[1])
+		)
+
+		messages = []interface{}{currentTime}
+
+		if level == "sql" {
+			// duration
+			messages = append(messages, fmt.Sprintf("[%.2fms]", float64(values[2].(time.Duration).Nanoseconds()/1e4)/100.0))
+			// sql
+			for _, value := range values[4].([]interface{}) {
+				indirectValue := reflect.Indirect(reflect.ValueOf(value))
+				if indirectValue.IsValid() {
+					value = indirectValue.Interface()
+					if t, ok := value.(time.Time); ok {
+						formattedValues = append(formattedValues, fmt.Sprintf("'%v'", t.Format("2006-01-02 15:04:05")))
+					} else if b, ok := value.([]byte); ok {
+						if str := string(b); isPrintable(str) {
+							formattedValues = append(formattedValues, fmt.Sprintf("'%v'", str))
+						} else {
+							formattedValues = append(formattedValues, "'<binary>'")
+						}
+					} else if r, ok := value.(driver.Valuer); ok {
+						if value, err := r.Value(); err == nil && value != nil {
+							formattedValues = append(formattedValues, fmt.Sprintf("'%v'", value))
+						} else {
+							formattedValues = append(formattedValues, "NULL")
+						}
+					} else {
+						formattedValues = append(formattedValues, fmt.Sprintf("'%v'", value))
+					}
+				} else {
+					formattedValues = append(formattedValues, "NULL")
+				}
+			}
+
+			// differentiate between $n placeholders or else treat like ?
+			if numericPlaceHolderRegexp.MatchString(values[3].(string)) {
+				sql = values[3].(string)
+				for index, value := range formattedValues {
+					placeholder := fmt.Sprintf(`\$%d([^\d]|$)`, index+1)
+					sql = regexp.MustCompile(placeholder).ReplaceAllString(sql, value+"$1")
+				}
+			} else {
+				formattedValuesLength := len(formattedValues)
+				for index, value := range sqlRegexp.Split(values[3].(string), -1) {
+					sql += value
+					if index < formattedValuesLength {
+						sql += formattedValues[index]
+					}
+				}
+			}
+
+			messages = append(messages, sql)
+			messages = append(messages, fmt.Sprintf("\r\n%v", strconv.FormatInt(values[5].(int64), 10)+" rows affected or returned"))
+		} else {
+			messages = append(messages, values[2:]...)
+		}
+	}
+
+	return
+}
+
+type logger interface {
+	Print(v ...interface{})
+}
+
+// Logger default logger
+type GormLogger struct {
+	*log.Logger
+}
+
+// Print format & print log
+func (logger GormLogger) Print(values ...interface{}) {
+	logger.Println(LogFormatter(values...)...)
+}

+ 22 - 0
utils/http.go

@@ -0,0 +1,22 @@
+package utils
+
+import (
+	"net/http"
+	"net/url"
+	"strings"
+)
+
+func GetRequestHref(r *http.Request) string {
+	scheme := "http://"
+	if r.TLS != nil {
+		scheme = "https://"
+	}
+	return strings.Join([]string{scheme, r.Host, r.RequestURI}, "")
+}
+
+func GetRequestHostname(r *http.Request) (hostname string) {
+	if _url, err := url.Parse(GetRequestHref(r)); err == nil {
+		hostname = _url.Hostname()
+	}
+	return
+}

+ 26 - 0
utils/log_debug.go

@@ -0,0 +1,26 @@
+// +build !release
+
+package utils
+
+import (
+	"fmt"
+	"io"
+	"log"
+	"os"
+)
+
+func Log(msg ...interface{}) {
+	log.Output(2, fmt.Sprintln(msg...))
+}
+
+func Logf(format string, msg ...interface{}) {
+	log.Output(2, fmt.Sprintf(format, msg...))
+}
+
+func GetLogWriter() io.Writer {
+	return os.Stdout
+}
+
+func CloseLogWriter() {
+
+}

+ 19 - 0
utils/log_linux.go

@@ -0,0 +1,19 @@
+package utils
+
+import (
+	"os"
+	"syscall"
+)
+
+// RedirectStderr to the file passed in
+func RedirectStderr() (err error) {
+	logFile, err := os.OpenFile(ErrorLogFilename(), os.O_WRONLY|os.O_CREATE|os.O_SYNC|os.O_APPEND, 0644)
+	if err != nil {
+		return
+	}
+	err = syscall.Dup2(int(logFile.Fd()), int(os.Stderr.Fd()))
+	if err != nil {
+		return
+	}
+	return
+}

+ 43 - 0
utils/log_release.go

@@ -0,0 +1,43 @@
+// +build release
+
+package utils
+
+import (
+	"io"
+	"log"
+	"os"
+	"path/filepath"
+	"strings"
+
+	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
+)
+
+func Log(msg ...interface{}) {}
+
+func Logf(format string, msg ...interface{}) {}
+
+var rl *rotatelogs.RotateLogs
+
+func GetLogWriter() io.Writer {
+	if rl != nil {
+		return rl
+	}
+	logDir := LogDir()
+	logFile := filepath.Join(logDir, strings.ToLower(EXEName())+"-%Y%m%d.log")
+	_rl, err := rotatelogs.New(logFile, rotatelogs.WithMaxAge(-1), rotatelogs.WithRotationCount(3))
+	if err == nil {
+		rl = _rl
+		return rl
+	}
+	log.Println("failed to create rotatelogs", err)
+	log.Println("use stdout")
+	return os.Stdout
+}
+
+func CloseLogWriter() {
+	log.SetOutput(os.Stdout)
+	if rl != nil {
+		rl.Close()
+		rl = nil
+	}
+}

+ 37 - 0
utils/log_windows.go

@@ -0,0 +1,37 @@
+package utils
+
+import (
+	"os"
+	"syscall"
+)
+
+var (
+	kernel32         = syscall.MustLoadDLL("kernel32.dll")
+	procSetStdHandle = kernel32.MustFindProc("SetStdHandle")
+)
+
+func setStdHandle(stdhandle int32, handle syscall.Handle) error {
+	r0, _, e1 := syscall.Syscall(procSetStdHandle.Addr(), 2, uintptr(stdhandle), uintptr(handle), 0)
+	if r0 == 0 {
+		if e1 != 0 {
+			return error(e1)
+		}
+		return syscall.EINVAL
+	}
+	return nil
+}
+
+// RedirectStderr to the file passed in
+func RedirectStderr() (err error) {
+	logFile, err := os.OpenFile(ErrorLogFilename(), os.O_WRONLY|os.O_CREATE|os.O_SYNC|os.O_APPEND, 0644)
+	if err != nil {
+		return
+	}
+	err = setStdHandle(syscall.STD_ERROR_HANDLE, syscall.Handle(logFile.Fd()))
+	if err != nil {
+		return
+	}
+	// SetStdHandle does not affect prior references to stderr
+	os.Stderr = logFile
+	return
+}

+ 143 - 0
utils/paging.go

@@ -0,0 +1,143 @@
+package utils
+
+import (
+	"fmt"
+	"log"
+	"math"
+	"reflect"
+	"sort"
+	"strings"
+)
+
+type PageForm struct {
+	Start int    `form:"start"`
+	Limit int    `form:"limit"`
+	Q     string `form:"q"`
+	Sort  string `form:"sort"`
+	Order string `form:"order"`
+}
+
+func (p PageForm) String() string {
+	return fmt.Sprintf("PageForm[Start=%d, Limit=%d, Q=%s, Sort=%s, Order=%s]", p.Start, p.Limit, p.Q, p.Sort, p.Order)
+}
+
+func NewPageForm() *PageForm {
+	return &PageForm{
+		Start: 0,
+		Limit: math.MaxInt32,
+	}
+}
+
+type PageResult struct {
+	Total int         `json:"total"`
+	Rows  interface{} `json:"rows"`
+}
+
+func NewPageResult(rows interface{}) *PageResult {
+	v := reflect.ValueOf(rows)
+	if v.Kind() == reflect.Slice {
+		return &PageResult{
+			Total: v.Len(),
+			Rows:  rows,
+		}
+	} else {
+		return &PageResult{
+			Total: 1,
+			Rows:  []interface{}{rows},
+		}
+	}
+}
+
+func (pr *PageResult) Slice(start, limit int) *PageResult {
+	if limit < 0 || start < 0 {
+		return pr
+	}
+	if pr.Rows == nil {
+		return pr
+	}
+	v := reflect.ValueOf(pr.Rows)
+	if v.Kind() != reflect.Slice {
+		return pr
+	}
+	_start := start
+	if _start > pr.Total {
+		_start = pr.Total
+	}
+	_end := start + limit
+	if _end > pr.Total {
+		_end = pr.Total
+	}
+	size := _end - _start
+	_rows := make([]interface{}, size)
+	for i := 0; i < size; i++ {
+		_rows[i] = v.Index(_start + i).Interface()
+	}
+	pr.Rows = _rows
+	return pr
+}
+
+func (pr *PageResult) Sort(by, order string) *PageResult {
+	if by == "" {
+		return pr
+	}
+	if reflect.TypeOf(pr.Rows).Kind() != reflect.Slice {
+		return pr
+	}
+	if reflect.ValueOf(pr.Rows).Len() == 0 {
+		return pr
+	}
+	te := reflect.TypeOf(pr.Rows).Elem()
+	for te.Kind() == reflect.Array || te.Kind() == reflect.Chan || te.Kind() == reflect.Map || te.Kind() == reflect.Ptr || te.Kind() == reflect.Slice {
+		te = te.Elem()
+	}
+	if te.Kind() == reflect.Interface {
+		va := reflect.ValueOf(pr.Rows).Index(0)
+		for va.Kind() == reflect.Interface || va.Kind() == reflect.Ptr {
+			va = va.Elem()
+		}
+		te = va.Type()
+	}
+	byIdx := -1
+	if te.Kind() == reflect.Struct {
+		for i := 0; i < te.NumField(); i++ {
+			if strings.EqualFold(te.Field(i).Name, by) {
+				// log.Printf("%v field name[%s] find field[%s] index[%d], case insensitive", te, by, te.Field(i).Name, i)
+				byIdx = i
+				break
+			}
+		}
+		if byIdx == -1 {
+			log.Printf("%v field name[%s] not found, case insensitive", te, by)
+			return pr
+		}
+	}
+	sort.Slice(pr.Rows, func(i, j int) (ret bool) {
+		va := reflect.ValueOf(pr.Rows).Index(i)
+		vb := reflect.ValueOf(pr.Rows).Index(j)
+		for va.Kind() == reflect.Interface || va.Kind() == reflect.Ptr {
+			va = va.Elem()
+		}
+		for vb.Kind() == reflect.Interface || vb.Kind() == reflect.Ptr {
+			vb = vb.Elem()
+		}
+		if va.Kind() == reflect.Struct && vb.Kind() == reflect.Struct {
+			switch va.Field(byIdx).Kind() {
+			case reflect.Float32, reflect.Float64:
+				ret = va.Field(byIdx).Float() < vb.Field(byIdx).Float()
+			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+				ret = va.Field(byIdx).Int() < vb.Field(byIdx).Int()
+			case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+				ret = va.Field(byIdx).Uint() < vb.Field(byIdx).Uint()
+			default:
+				ret = fmt.Sprintf("%v", va.Field(byIdx)) < fmt.Sprintf("%v", vb.Field(byIdx))
+			}
+		} else if va.Kind() == reflect.Map && vb.Kind() == reflect.Map {
+			ret = fmt.Sprintf("%v", va.MapIndex(reflect.ValueOf(by))) < fmt.Sprintf("%v", vb.MapIndex(reflect.ValueOf(by)))
+		}
+		if strings.HasPrefix(strings.ToLower(order), "desc") {
+			ret = !ret
+		}
+		return
+	})
+	return pr
+}

+ 18 - 0
utils/statik-file-system.go

@@ -0,0 +1,18 @@
+package utils
+
+import (
+	"net/http"
+	"strings"
+)
+
+type StatikFileSystem struct {
+	http.FileSystem
+}
+
+func (s *StatikFileSystem) Exists(prefix string, filepath string) bool {
+	if p := strings.TrimPrefix(filepath, prefix); len(p) < len(filepath) {
+		_, err := s.Open("/" + p)
+		return err == nil
+	}
+	return false
+}

+ 38 - 0
utils/string.go

@@ -0,0 +1,38 @@
+package utils
+
+import (
+	"encoding/json"
+	"regexp"
+	"strings"
+	"unicode"
+)
+
+type StringArray string
+
+func (r StringArray) MarshalJSON() ([]byte, error) {
+	items := []string{}
+	if string(r) != "" {
+		items = strings.Split(string(r), ",")
+	}
+	for _, item := range items {
+		item = strings.TrimSpace(item)
+	}
+	return json.Marshal(items)
+}
+
+func Ellipsis(text string, length int) string {
+	r := []rune(text)
+	if len(r) > length {
+		return string(r[0:length]) + "..."
+	}
+	return text
+}
+
+func HasChinese(str string) bool {
+	for _, r := range str {
+		if unicode.Is(unicode.Scripts["Han"], r) || (regexp.MustCompile("[\u3002\uff1b\uff0c\uff1a\u201c\u201d\uff08\uff09\u3001\uff1f\u300a\u300b]").MatchString(string(r))) {
+			return true
+		}
+	}
+	return false
+}

+ 69 - 0
utils/time.go

@@ -0,0 +1,69 @@
+package utils
+
+import (
+	"database/sql/driver"
+	"fmt"
+	"time"
+)
+
+type DateTime time.Time
+
+const (
+	DateLayout      = "2006-01-02"
+	DateTimeLayout  = "2006-01-02 15:04:05"
+	BuildTimeLayout = "2006.0102.150405"
+	TimestampLayout = "20060102150405"
+)
+
+var StartTime = time.Now()
+
+func (dt *DateTime) UnmarshalJSON(data []byte) (err error) {
+	now, err := time.ParseInLocation(DateTimeLayout, string(data), time.Local)
+	*dt = DateTime(now)
+	return
+}
+
+func (dt DateTime) MarshalJSON() ([]byte, error) {
+	b := make([]byte, 0, len(DateTimeLayout)+2)
+	b = append(b, '"')
+	b = time.Time(dt).AppendFormat(b, DateTimeLayout)
+	b = append(b, '"')
+	return b, nil
+}
+
+func (dt DateTime) Value() (driver.Value, error) {
+	var zeroTime time.Time
+	ti := time.Time(dt)
+	if ti.UnixNano() == zeroTime.UnixNano() {
+		return nil, nil
+	}
+	return ti, nil
+}
+
+func (dt *DateTime) Scan(v interface{}) error {
+	if value, ok := v.(time.Time); ok {
+		*dt = DateTime(value)
+		return nil
+	}
+	return nil
+}
+
+func (dt DateTime) String() string {
+	return time.Time(dt).Format(DateTimeLayout)
+}
+
+func UpTime() time.Duration {
+	return time.Since(StartTime)
+}
+
+func UpTimeString() string {
+	d := UpTime()
+	days := d / (time.Hour * 24)
+	d -= days * 24 * time.Hour
+	hours := d / time.Hour
+	d -= hours * time.Hour
+	minutes := d / time.Minute
+	d -= minutes * time.Minute
+	seconds := d / time.Second
+	return fmt.Sprintf("%d Days %d Hours %d Mins %d Secs", days, hours, minutes, seconds)
+}

+ 256 - 0
utils/utils.go

@@ -0,0 +1,256 @@
+package utils
+
+import (
+	"bytes"
+	"crypto/md5"
+	"encoding/gob"
+	"encoding/hex"
+	"fmt"
+	"log"
+	"net"
+	"os"
+	"os/exec"
+	"os/user"
+	"path/filepath"
+	"runtime"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/eiannone/keyboard"
+	"github.com/teris-io/shortid"
+
+	"github.com/go-ini/ini"
+)
+
+func LocalIP() string {
+	ip := ""
+	if addrs, err := net.InterfaceAddrs(); err == nil {
+		for _, addr := range addrs {
+			if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && !ipnet.IP.IsMulticast() && !ipnet.IP.IsLinkLocalUnicast() && !ipnet.IP.IsLinkLocalMulticast() && ipnet.IP.To4() != nil {
+				ip = ipnet.IP.String()
+			}
+		}
+	}
+	return ip
+}
+
+func MD5(str string) string {
+	encoder := md5.New()
+	encoder.Write([]byte(str))
+	return hex.EncodeToString(encoder.Sum(nil))
+}
+
+func CWD() string {
+	path, err := os.Executable()
+	if err != nil {
+		return ""
+	}
+	return filepath.Dir(path)
+}
+
+var workInDirLock sync.Mutex
+
+func WorkInDir(f func(), dir string) {
+	wd, _ := os.Getwd()
+	workInDirLock.Lock()
+	defer workInDirLock.Unlock()
+	os.Chdir(dir)
+	defer os.Chdir(wd)
+	f()
+}
+
+func EXEName() string {
+	path, err := os.Executable()
+	if err != nil {
+		return ""
+	}
+	return strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
+}
+
+func HomeDir() string {
+	u, err := user.Current()
+	if err != nil {
+		return ""
+	}
+	return u.HomeDir
+}
+
+func LogDir() string {
+	dir := filepath.Join(CWD(), "logs")
+	EnsureDir(dir)
+	return dir
+}
+
+func ErrorLogFilename() string {
+	return filepath.Join(LogDir(), fmt.Sprintf("%s-error.log", strings.ToLower(EXEName())))
+}
+
+func DataDir() string {
+	dir := CWD()
+	_dir := Conf().Section("").Key("data_dir").Value()
+	if _dir != "" {
+		dir = _dir
+	}
+	dir = ExpandHomeDir(dir)
+	EnsureDir(dir)
+	return dir
+}
+
+var FlagVarConfFile string
+
+func ConfFile() string {
+	if FlagVarConfFile != "" {
+		return FlagVarConfFile
+	}
+	if Exist(ConfFileDev()) {
+		return ConfFileDev()
+	}
+	return filepath.Join(CWD(), strings.ToLower(EXEName())+".ini")
+}
+
+func ConfFileDev() string {
+	return filepath.Join(CWD(), strings.ToLower(EXEName())+".dev.ini")
+}
+
+var FlagVarDBFile string
+
+func DBFile() string {
+	if FlagVarDBFile != "" {
+		return FlagVarDBFile
+	}
+	if Exist(DBFileDev()) {
+		return DBFileDev()
+	}
+	return filepath.Join(CWD(), strings.ToLower(EXEName()+".db"))
+}
+
+func DBFileDev() string {
+	return filepath.Join(CWD(), strings.ToLower(EXEName())+".dev.db")
+}
+
+var conf *ini.File
+
+func Conf() *ini.File {
+	if conf != nil {
+		return conf
+	}
+	if _conf, err := ini.InsensitiveLoad(ConfFile()); err != nil {
+		_conf, _ = ini.LoadSources(ini.LoadOptions{Insensitive: true}, []byte(""))
+		conf = _conf
+	} else {
+		conf = _conf
+	}
+	return conf
+}
+
+func ReloadConf() *ini.File {
+	if _conf, err := ini.InsensitiveLoad(ConfFile()); err != nil {
+		_conf, _ = ini.LoadSources(ini.LoadOptions{Insensitive: true}, []byte(""))
+		conf = _conf
+	} else {
+		conf = _conf
+	}
+	return conf
+}
+
+func SaveToConf(section string, kvmap map[string]string) error {
+	var _conf *ini.File
+	var err error
+	if _conf, err = ini.InsensitiveLoad(ConfFile()); err != nil {
+		if _conf, err = ini.LoadSources(ini.LoadOptions{Insensitive: true}, []byte("")); err != nil {
+			return err
+		}
+	}
+	sec := _conf.Section(section)
+	for k, v := range kvmap {
+		sec.Key(k).SetValue(v)
+	}
+	_conf.SaveTo(ConfFile())
+	conf = _conf
+	return nil
+}
+
+func ExpandHomeDir(path string) string {
+	if len(path) == 0 {
+		return path
+	}
+	if path[0] != '~' {
+		return path
+	}
+	if len(path) > 1 && path[1] != '/' && path[1] != '\\' {
+		return path
+	}
+	return filepath.Join(HomeDir(), path[1:])
+}
+
+func EnsureDir(dir string) (err error) {
+	if _, err = os.Stat(dir); os.IsNotExist(err) {
+		err = os.MkdirAll(dir, 0755)
+		if err != nil {
+			return
+		}
+	}
+	return
+}
+
+func Exist(path string) bool {
+	if _, err := os.Stat(path); os.IsNotExist(err) {
+		return false
+	}
+	return true
+}
+
+func DeepCopy(dst, src interface{}) error {
+	var buf bytes.Buffer
+	if err := gob.NewEncoder(&buf).Encode(src); err != nil {
+		return err
+	}
+	return gob.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(dst)
+}
+
+func Open(url string) error {
+	var cmd string
+	var args []string
+
+	switch runtime.GOOS {
+	case "windows":
+		cmd = "cmd"
+		args = []string{"/c", "start"}
+	case "darwin":
+		cmd = "open"
+	default: // "linux", "freebsd", "openbsd", "netbsd"
+		cmd = "xdg-open"
+	}
+	args = append(args, url)
+	return exec.Command(cmd, args...).Start()
+}
+
+func ShortID() string {
+	return shortid.MustGenerate()
+}
+
+func PauseExit() {
+	log.Println("Press any to exit")
+	keyboard.GetSingleKey()
+	os.Exit(0)
+}
+
+func PauseGo(msg ...interface{}) {
+	log.Println(msg...)
+	keyboard.GetSingleKey()
+}
+
+func IsPortInUse(port int) bool {
+	if conn, err := net.DialTimeout("tcp", net.JoinHostPort("", fmt.Sprintf("%d", port)), 3*time.Second); err == nil {
+		conn.Close()
+		return true
+	}
+	return false
+}
+
+func init() {
+	gob.Register(map[string]interface{}{})
+	gob.Register(StringArray(""))
+	ini.PrettyFormat = false
+}

+ 5 - 0
utils/utils_debug.go

@@ -0,0 +1,5 @@
+// +build !release
+
+package utils
+
+var Debug = true

+ 5 - 0
utils/utils_release.go

@@ -0,0 +1,5 @@
+// +build release
+
+package utils
+
+var Debug = false