ls 2 years ago
parent
commit
3d8526c70a

+ 8 - 0
.gitignore

@@ -7,6 +7,7 @@
 # Folders
 _obj
 _test
+bin
 
 # Architecture specific extensions/prefixes
 *.[568vq]
@@ -24,3 +25,10 @@ _testmain.go
 *.test
 *.prof
 
+.vscode
+.settings
+.metals
+*.json
+*.log
+*.DS_Store
+*.rdb

+ 126 - 0
binding/binding.go

@@ -0,0 +1,126 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+//go:build !nomsgpack
+// +build !nomsgpack
+
+package binding
+
+import "net/http"
+
+// Content-Type MIME of the most common data formats.
+const (
+	MIMEJSON              = "application/json"
+	MIMEHTML              = "text/html"
+	MIMEXML               = "application/xml"
+	MIMEXML2              = "text/xml"
+	MIMEPlain             = "text/plain"
+	MIMEPOSTForm          = "application/x-www-form-urlencoded"
+	MIMEMultipartPOSTForm = "multipart/form-data"
+	MIMEPROTOBUF          = "application/x-protobuf"
+	MIMEMSGPACK           = "application/x-msgpack"
+	MIMEMSGPACK2          = "application/msgpack"
+	MIMEYAML              = "application/x-yaml"
+)
+
+// Binding describes the interface which needs to be implemented for binding the
+// data present in the request such as JSON request body, query parameters or
+// the form POST.
+type Binding interface {
+	Name() string
+	Bind(*http.Request, interface{}) error
+}
+
+// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
+// but it reads the body from supplied bytes instead of req.Body.
+type BindingBody interface {
+	Binding
+	BindBody([]byte, interface{}) error
+}
+
+// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
+// but it read the Params.
+type BindingUri interface {
+	Name() string
+	BindUri(map[string][]string, interface{}) error
+}
+
+// These implement the Binding interface and can be used to bind the data
+// present in the request to struct instances.
+var (
+	JSON          = jsonBinding{}
+	XML           = xmlBinding{}
+	Form          = formBinding{}
+	Query         = queryBinding{}
+	FormPost      = formPostBinding{}
+	FormMultipart = formMultipartBinding{}
+	ProtoBuf      = protobufBinding{}
+	MsgPack       = msgpackBinding{}
+	YAML          = yamlBinding{}
+	Uri           = uriBinding{}
+	Header        = headerBinding{}
+)
+
+// Default returns the appropriate Binding instance based on the HTTP method
+// and the content type.
+func Default(method, contentType string) Binding {
+	if method == http.MethodGet {
+		return Form
+	}
+
+	switch contentType {
+	case MIMEJSON:
+		return JSON
+	case MIMEXML, MIMEXML2:
+		return XML
+	case MIMEPROTOBUF:
+		return ProtoBuf
+	case MIMEMSGPACK, MIMEMSGPACK2:
+		return MsgPack
+	case MIMEYAML:
+		return YAML
+	case MIMEMultipartPOSTForm:
+		return FormMultipart
+	default: // case MIMEPOSTForm:
+		return Form
+	}
+}
+
+func validate(obj interface{}) error {
+	return nil
+}
+
+// Bind checks the Content-Type to select a binding engine automatically,
+// Depending the "Content-Type" header different bindings are used:
+//     "application/json" --> JSON binding
+//     "application/xml"  --> XML binding
+// otherwise --> returns an error.
+// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
+// It decodes the json payload into the struct specified as a pointer.
+// It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
+func Bind(req *http.Request, obj interface{}) error {
+	b := Default(req.Method, ContentType(req))
+	return MustBindWith(req, obj, b)
+}
+
+// MustBindWith binds the passed struct pointer using the specified binding engine.
+// It will abort the request with HTTP 400 if any error occurs.
+// See the binding package.
+func MustBindWith(req *http.Request, obj interface{}, b Binding) (err error) {
+	return b.Bind(req, obj)
+}
+
+// ContentType returns the Content-Type header of the request.
+func ContentType(req *http.Request) string {
+	return filterFlags(req.Header.Get("Content-Type"))
+}
+
+func filterFlags(content string) string {
+	for i, char := range content {
+		if char == ' ' || char == ';' {
+			return content[:i]
+		}
+	}
+	return content
+}

+ 112 - 0
binding/binding_nomsgpack.go

@@ -0,0 +1,112 @@
+// Copyright 2020 Gin Core Team. All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+//go:build nomsgpack
+// +build nomsgpack
+
+package binding
+
+import "net/http"
+
+// Content-Type MIME of the most common data formats.
+const (
+	MIMEJSON              = "application/json"
+	MIMEHTML              = "text/html"
+	MIMEXML               = "application/xml"
+	MIMEXML2              = "text/xml"
+	MIMEPlain             = "text/plain"
+	MIMEPOSTForm          = "application/x-www-form-urlencoded"
+	MIMEMultipartPOSTForm = "multipart/form-data"
+	MIMEPROTOBUF          = "application/x-protobuf"
+	MIMEYAML              = "application/x-yaml"
+)
+
+// Binding describes the interface which needs to be implemented for binding the
+// data present in the request such as JSON request body, query parameters or
+// the form POST.
+type Binding interface {
+	Name() string
+	Bind(*http.Request, interface{}) error
+}
+
+// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
+// but it reads the body from supplied bytes instead of req.Body.
+type BindingBody interface {
+	Binding
+	BindBody([]byte, interface{}) error
+}
+
+// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
+// but it read the Params.
+type BindingUri interface {
+	Name() string
+	BindUri(map[string][]string, interface{}) error
+}
+
+// StructValidator is the minimal interface which needs to be implemented in
+// order for it to be used as the validator engine for ensuring the correctness
+// of the request. Gin provides a default implementation for this using
+// https://github.com/go-playground/validator/tree/v8.18.2.
+type StructValidator interface {
+	// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
+	// If the received type is not a struct, any validation should be skipped and nil must be returned.
+	// If the received type is a struct or pointer to a struct, the validation should be performed.
+	// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
+	// Otherwise nil must be returned.
+	ValidateStruct(interface{}) error
+
+	// Engine returns the underlying validator engine which powers the
+	// StructValidator implementation.
+	Engine() interface{}
+}
+
+// Validator is the default validator which implements the StructValidator
+// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2
+// under the hood.
+var Validator StructValidator = &defaultValidator{}
+
+// These implement the Binding interface and can be used to bind the data
+// present in the request to struct instances.
+var (
+	JSON          = jsonBinding{}
+	XML           = xmlBinding{}
+	Form          = formBinding{}
+	Query         = queryBinding{}
+	FormPost      = formPostBinding{}
+	FormMultipart = formMultipartBinding{}
+	ProtoBuf      = protobufBinding{}
+	YAML          = yamlBinding{}
+	Uri           = uriBinding{}
+	Header        = headerBinding{}
+)
+
+// Default returns the appropriate Binding instance based on the HTTP method
+// and the content type.
+func Default(method, contentType string) Binding {
+	if method == "GET" {
+		return Form
+	}
+
+	switch contentType {
+	case MIMEJSON:
+		return JSON
+	case MIMEXML, MIMEXML2:
+		return XML
+	case MIMEPROTOBUF:
+		return ProtoBuf
+	case MIMEYAML:
+		return YAML
+	case MIMEMultipartPOSTForm:
+		return FormMultipart
+	default: // case MIMEPOSTForm:
+		return Form
+	}
+}
+
+func validate(obj interface{}) error {
+	if Validator == nil {
+		return nil
+	}
+	return Validator.ValidateStruct(obj)
+}

+ 19 - 0
binding/bytesconv.go

@@ -0,0 +1,19 @@
+package binding
+
+import (
+	"reflect"
+	"unsafe"
+)
+
+// StringToBytes converts string to byte slice without a memory allocation.
+func StringToBytes(s string) (b []byte) {
+	sh := *(*reflect.StringHeader)(unsafe.Pointer(&s))
+	bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
+	bh.Data, bh.Len, bh.Cap = sh.Data, sh.Len, sh.Len
+	return b
+}
+
+// BytesToString converts byte slice to string without a memory allocation.
+func BytesToString(b []byte) string {
+	return *(*string)(unsafe.Pointer(&b))
+}

+ 63 - 0
binding/form.go

@@ -0,0 +1,63 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import (
+	"net/http"
+)
+
+const defaultMemory = 32 << 20
+
+type formBinding struct{}
+type formPostBinding struct{}
+type formMultipartBinding struct{}
+
+func (formBinding) Name() string {
+	return "form"
+}
+
+func (formBinding) Bind(req *http.Request, obj interface{}) error {
+	if err := req.ParseForm(); err != nil {
+		return err
+	}
+	if err := req.ParseMultipartForm(defaultMemory); err != nil {
+		if err != http.ErrNotMultipart {
+			return err
+		}
+	}
+	if err := mapForm(obj, req.Form); err != nil {
+		return err
+	}
+	return validate(obj)
+}
+
+func (formPostBinding) Name() string {
+	return "form-urlencoded"
+}
+
+func (formPostBinding) Bind(req *http.Request, obj interface{}) error {
+	if err := req.ParseForm(); err != nil {
+		return err
+	}
+	if err := mapForm(obj, req.PostForm); err != nil {
+		return err
+	}
+	return validate(obj)
+}
+
+func (formMultipartBinding) Name() string {
+	return "multipart/form-data"
+}
+
+func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error {
+	if err := req.ParseMultipartForm(defaultMemory); err != nil {
+		return err
+	}
+	if err := mappingByPtr(obj, (*multipartRequest)(req), "form"); err != nil {
+		return err
+	}
+
+	return validate(obj)
+}

+ 390 - 0
binding/form_mapping.go

@@ -0,0 +1,390 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"reflect"
+	"strconv"
+	"strings"
+	"time"
+)
+
+var errUnknownType = errors.New("unknown type")
+
+func mapUri(ptr interface{}, m map[string][]string) error {
+	return mapFormByTag(ptr, m, "uri")
+}
+
+func mapForm(ptr interface{}, form map[string][]string) error {
+	return mapFormByTag(ptr, form, "form")
+}
+
+var emptyField = reflect.StructField{}
+
+func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
+	// Check if ptr is a map
+	ptrVal := reflect.ValueOf(ptr)
+	var pointed interface{}
+	if ptrVal.Kind() == reflect.Ptr {
+		ptrVal = ptrVal.Elem()
+		pointed = ptrVal.Interface()
+	}
+	if ptrVal.Kind() == reflect.Map &&
+		ptrVal.Type().Key().Kind() == reflect.String {
+		if pointed != nil {
+			ptr = pointed
+		}
+		return setFormMap(ptr, form)
+	}
+
+	return mappingByPtr(ptr, formSource(form), tag)
+}
+
+// setter tries to set value on a walking by fields of a struct
+type setter interface {
+	TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error)
+}
+
+type formSource map[string][]string
+
+var _ setter = formSource(nil)
+
+// TrySet tries to set a value by request's form source (like map[string][]string)
+func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) {
+	return setByForm(value, field, form, tagValue, opt)
+}
+
+func mappingByPtr(ptr interface{}, setter setter, tag string) error {
+	_, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag)
+	return err
+}
+
+func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
+	if field.Tag.Get(tag) == "-" { // just ignoring this field
+		return false, nil
+	}
+
+	var vKind = value.Kind()
+
+	if vKind == reflect.Ptr {
+		var isNew bool
+		vPtr := value
+		if value.IsNil() {
+			isNew = true
+			vPtr = reflect.New(value.Type().Elem())
+		}
+		isSetted, err := mapping(vPtr.Elem(), field, setter, tag)
+		if err != nil {
+			return false, err
+		}
+		if isNew && isSetted {
+			value.Set(vPtr)
+		}
+		return isSetted, nil
+	}
+
+	if vKind != reflect.Struct || !field.Anonymous {
+		ok, err := tryToSetValue(value, field, setter, tag)
+		if err != nil {
+			return false, err
+		}
+		if ok {
+			return true, nil
+		}
+	}
+
+	if vKind == reflect.Struct {
+		tValue := value.Type()
+
+		var isSetted bool
+		for i := 0; i < value.NumField(); i++ {
+			sf := tValue.Field(i)
+			if sf.PkgPath != "" && !sf.Anonymous { // unexported
+				continue
+			}
+			ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag)
+			if err != nil {
+				return false, err
+			}
+			isSetted = isSetted || ok
+		}
+		return isSetted, nil
+	}
+	return false, nil
+}
+
+type setOptions struct {
+	isDefaultExists bool
+	defaultValue    string
+}
+
+func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
+	var tagValue string
+	var setOpt setOptions
+
+	tagValue = field.Tag.Get(tag)
+	tagValue, opts := head(tagValue, ",")
+
+	if tagValue == "" { // default value is FieldName
+		tagValue = field.Name
+	}
+	if tagValue == "" { // when field is "emptyField" variable
+		return false, nil
+	}
+
+	var opt string
+	for len(opts) > 0 {
+		opt, opts = head(opts, ",")
+
+		if k, v := head(opt, "="); k == "default" {
+			setOpt.isDefaultExists = true
+			setOpt.defaultValue = v
+		}
+	}
+
+	return setter.TrySet(value, field, tagValue, setOpt)
+}
+
+func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSetted bool, err error) {
+	vs, ok := form[tagValue]
+	if !ok && !opt.isDefaultExists {
+		return false, nil
+	}
+
+	switch value.Kind() {
+	case reflect.Slice:
+		if !ok {
+			vs = []string{opt.defaultValue}
+		}
+		return true, setSlice(vs, value, field)
+	case reflect.Array:
+		if !ok {
+			vs = []string{opt.defaultValue}
+		}
+		if len(vs) != value.Len() {
+			return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())
+		}
+		return true, setArray(vs, value, field)
+	default:
+		var val string
+		if !ok {
+			val = opt.defaultValue
+		}
+
+		if len(vs) > 0 {
+			val = vs[0]
+		}
+		return true, setWithProperType(val, value, field)
+	}
+}
+
+func setWithProperType(val string, value reflect.Value, field reflect.StructField) error {
+	switch value.Kind() {
+	case reflect.Int:
+		return setIntField(val, 0, value)
+	case reflect.Int8:
+		return setIntField(val, 8, value)
+	case reflect.Int16:
+		return setIntField(val, 16, value)
+	case reflect.Int32:
+		return setIntField(val, 32, value)
+	case reflect.Int64:
+		switch value.Interface().(type) {
+		case time.Duration:
+			return setTimeDuration(val, value, field)
+		}
+		return setIntField(val, 64, value)
+	case reflect.Uint:
+		return setUintField(val, 0, value)
+	case reflect.Uint8:
+		return setUintField(val, 8, value)
+	case reflect.Uint16:
+		return setUintField(val, 16, value)
+	case reflect.Uint32:
+		return setUintField(val, 32, value)
+	case reflect.Uint64:
+		return setUintField(val, 64, value)
+	case reflect.Bool:
+		return setBoolField(val, value)
+	case reflect.Float32:
+		return setFloatField(val, 32, value)
+	case reflect.Float64:
+		return setFloatField(val, 64, value)
+	case reflect.String:
+		value.SetString(val)
+	case reflect.Struct:
+		switch value.Interface().(type) {
+		case time.Time:
+			return setTimeField(val, field, value)
+		}
+		return json.Unmarshal(StringToBytes(val), value.Addr().Interface())
+	case reflect.Map:
+		return json.Unmarshal(StringToBytes(val), value.Addr().Interface())
+	default:
+		return errUnknownType
+	}
+	return nil
+}
+
+func setIntField(val string, bitSize int, field reflect.Value) error {
+	if val == "" {
+		val = "0"
+	}
+	intVal, err := strconv.ParseInt(val, 10, bitSize)
+	if err == nil {
+		field.SetInt(intVal)
+	}
+	return err
+}
+
+func setUintField(val string, bitSize int, field reflect.Value) error {
+	if val == "" {
+		val = "0"
+	}
+	uintVal, err := strconv.ParseUint(val, 10, bitSize)
+	if err == nil {
+		field.SetUint(uintVal)
+	}
+	return err
+}
+
+func setBoolField(val string, field reflect.Value) error {
+	if val == "" {
+		val = "false"
+	}
+	boolVal, err := strconv.ParseBool(val)
+	if err == nil {
+		field.SetBool(boolVal)
+	}
+	return err
+}
+
+func setFloatField(val string, bitSize int, field reflect.Value) error {
+	if val == "" {
+		val = "0.0"
+	}
+	floatVal, err := strconv.ParseFloat(val, bitSize)
+	if err == nil {
+		field.SetFloat(floatVal)
+	}
+	return err
+}
+
+func setTimeField(val string, structField reflect.StructField, value reflect.Value) error {
+	timeFormat := structField.Tag.Get("time_format")
+	if timeFormat == "" {
+		timeFormat = time.RFC3339
+	}
+
+	switch tf := strings.ToLower(timeFormat); tf {
+	case "unix", "unixnano":
+		tv, err := strconv.ParseInt(val, 10, 64)
+		if err != nil {
+			return err
+		}
+
+		d := time.Duration(1)
+		if tf == "unixnano" {
+			d = time.Second
+		}
+
+		t := time.Unix(tv/int64(d), tv%int64(d))
+		value.Set(reflect.ValueOf(t))
+		return nil
+
+	}
+
+	if val == "" {
+		value.Set(reflect.ValueOf(time.Time{}))
+		return nil
+	}
+
+	l := time.Local
+	if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC {
+		l = time.UTC
+	}
+
+	if locTag := structField.Tag.Get("time_location"); locTag != "" {
+		loc, err := time.LoadLocation(locTag)
+		if err != nil {
+			return err
+		}
+		l = loc
+	}
+
+	t, err := time.ParseInLocation(timeFormat, val, l)
+	if err != nil {
+		return err
+	}
+
+	value.Set(reflect.ValueOf(t))
+	return nil
+}
+
+func setArray(vals []string, value reflect.Value, field reflect.StructField) error {
+	for i, s := range vals {
+		err := setWithProperType(s, value.Index(i), field)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func setSlice(vals []string, value reflect.Value, field reflect.StructField) error {
+	slice := reflect.MakeSlice(value.Type(), len(vals), len(vals))
+	err := setArray(vals, slice, field)
+	if err != nil {
+		return err
+	}
+	value.Set(slice)
+	return nil
+}
+
+func setTimeDuration(val string, value reflect.Value, field reflect.StructField) error {
+	d, err := time.ParseDuration(val)
+	if err != nil {
+		return err
+	}
+	value.Set(reflect.ValueOf(d))
+	return nil
+}
+
+func head(str, sep string) (head string, tail string) {
+	idx := strings.Index(str, sep)
+	if idx < 0 {
+		return str, ""
+	}
+	return str[:idx], str[idx+len(sep):]
+}
+
+func setFormMap(ptr interface{}, form map[string][]string) error {
+	el := reflect.TypeOf(ptr).Elem()
+
+	if el.Kind() == reflect.Slice {
+		ptrMap, ok := ptr.(map[string][]string)
+		if !ok {
+			return errors.New("cannot convert to map slices of strings")
+		}
+		for k, v := range form {
+			ptrMap[k] = v
+		}
+
+		return nil
+	}
+
+	ptrMap, ok := ptr.(map[string]string)
+	if !ok {
+		return errors.New("cannot convert to map of strings")
+	}
+	for k, v := range form {
+		ptrMap[k] = v[len(v)-1] // pick last
+	}
+
+	return nil
+}

+ 34 - 0
binding/header.go

@@ -0,0 +1,34 @@
+package binding
+
+import (
+	"net/http"
+	"net/textproto"
+	"reflect"
+)
+
+type headerBinding struct{}
+
+func (headerBinding) Name() string {
+	return "header"
+}
+
+func (headerBinding) Bind(req *http.Request, obj interface{}) error {
+
+	if err := mapHeader(obj, req.Header); err != nil {
+		return err
+	}
+
+	return validate(obj)
+}
+
+func mapHeader(ptr interface{}, h map[string][]string) error {
+	return mappingByPtr(ptr, headerSource(h), "header")
+}
+
+type headerSource map[string][]string
+
+var _ setter = headerSource(nil)
+
+func (hs headerSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) {
+	return setByForm(value, field, hs, textproto.CanonicalMIMEHeaderKey(tagValue), opt)
+}

+ 55 - 0
binding/json.go

@@ -0,0 +1,55 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+)
+
+// EnableDecoderUseNumber is used to call the UseNumber method on the JSON
+// Decoder instance. UseNumber causes the Decoder to unmarshal a number into an
+// interface{} as a Number instead of as a float64.
+var EnableDecoderUseNumber = false
+
+// EnableDecoderDisallowUnknownFields is used to call the DisallowUnknownFields method
+// on the JSON Decoder instance. DisallowUnknownFields causes the Decoder to
+// return an error when the destination is a struct and the input contains object
+// keys which do not match any non-ignored, exported fields in the destination.
+var EnableDecoderDisallowUnknownFields = false
+
+type jsonBinding struct{}
+
+func (jsonBinding) Name() string {
+	return "json"
+}
+
+func (jsonBinding) Bind(req *http.Request, obj interface{}) error {
+	if req == nil || req.Body == nil {
+		return fmt.Errorf("invalid request")
+	}
+	return decodeJSON(req.Body, obj)
+}
+
+func (jsonBinding) BindBody(body []byte, obj interface{}) error {
+	return decodeJSON(bytes.NewReader(body), obj)
+}
+
+func decodeJSON(r io.Reader, obj interface{}) error {
+	decoder := json.NewDecoder(r)
+	if EnableDecoderUseNumber {
+		decoder.UseNumber()
+	}
+	if EnableDecoderDisallowUnknownFields {
+		decoder.DisallowUnknownFields()
+	}
+	if err := decoder.Decode(obj); err != nil {
+		return err
+	}
+	return validate(obj)
+}

+ 38 - 0
binding/msgpack.go

@@ -0,0 +1,38 @@
+// Copyright 2017 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+//go:build !nomsgpack
+// +build !nomsgpack
+
+package binding
+
+import (
+	"bytes"
+	"io"
+	"net/http"
+
+	"github.com/ugorji/go/codec"
+)
+
+type msgpackBinding struct{}
+
+func (msgpackBinding) Name() string {
+	return "msgpack"
+}
+
+func (msgpackBinding) Bind(req *http.Request, obj interface{}) error {
+	return decodeMsgPack(req.Body, obj)
+}
+
+func (msgpackBinding) BindBody(body []byte, obj interface{}) error {
+	return decodeMsgPack(bytes.NewReader(body), obj)
+}
+
+func decodeMsgPack(r io.Reader, obj interface{}) error {
+	cdc := new(codec.MsgpackHandle)
+	if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil {
+		return err
+	}
+	return validate(obj)
+}

+ 66 - 0
binding/multipart_form_mapping.go

@@ -0,0 +1,66 @@
+// Copyright 2019 Gin Core Team.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import (
+	"errors"
+	"mime/multipart"
+	"net/http"
+	"reflect"
+)
+
+type multipartRequest http.Request
+
+var _ setter = (*multipartRequest)(nil)
+
+// TrySet tries to set a value by the multipart request with the binding a form file
+func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) {
+	if files := r.MultipartForm.File[key]; len(files) != 0 {
+		return setByMultipartFormFile(value, field, files)
+	}
+
+	return setByForm(value, field, r.MultipartForm.Value, key, opt)
+}
+
+func setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) {
+	switch value.Kind() {
+	case reflect.Ptr:
+		switch value.Interface().(type) {
+		case *multipart.FileHeader:
+			value.Set(reflect.ValueOf(files[0]))
+			return true, nil
+		}
+	case reflect.Struct:
+		switch value.Interface().(type) {
+		case multipart.FileHeader:
+			value.Set(reflect.ValueOf(*files[0]))
+			return true, nil
+		}
+	case reflect.Slice:
+		slice := reflect.MakeSlice(value.Type(), len(files), len(files))
+		isSetted, err = setArrayOfMultipartFormFiles(slice, field, files)
+		if err != nil || !isSetted {
+			return isSetted, err
+		}
+		value.Set(slice)
+		return true, nil
+	case reflect.Array:
+		return setArrayOfMultipartFormFiles(value, field, files)
+	}
+	return false, errors.New("unsupported field type for multipart.FileHeader")
+}
+
+func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) {
+	if value.Len() != len(files) {
+		return false, errors.New("unsupported len of array for []*multipart.FileHeader")
+	}
+	for i := range files {
+		setted, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1])
+		if err != nil || !setted {
+			return setted, err
+		}
+	}
+	return true, nil
+}

+ 36 - 0
binding/protobuf.go

@@ -0,0 +1,36 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import (
+	"io/ioutil"
+	"net/http"
+
+	"github.com/golang/protobuf/proto"
+)
+
+type protobufBinding struct{}
+
+func (protobufBinding) Name() string {
+	return "protobuf"
+}
+
+func (b protobufBinding) Bind(req *http.Request, obj interface{}) error {
+	buf, err := ioutil.ReadAll(req.Body)
+	if err != nil {
+		return err
+	}
+	return b.BindBody(buf, obj)
+}
+
+func (protobufBinding) BindBody(body []byte, obj interface{}) error {
+	if err := proto.Unmarshal(body, obj.(proto.Message)); err != nil {
+		return err
+	}
+	// Here it's same to return validate(obj), but util now we can't add
+	// `binding:""` to the struct which automatically generate by gen-proto
+	return nil
+	// return validate(obj)
+}

+ 21 - 0
binding/query.go

@@ -0,0 +1,21 @@
+// Copyright 2017 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import "net/http"
+
+type queryBinding struct{}
+
+func (queryBinding) Name() string {
+	return "query"
+}
+
+func (queryBinding) Bind(req *http.Request, obj interface{}) error {
+	values := req.URL.Query()
+	if err := mapForm(obj, values); err != nil {
+		return err
+	}
+	return validate(obj)
+}

+ 18 - 0
binding/uri.go

@@ -0,0 +1,18 @@
+// Copyright 2018 Gin Core Team.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+type uriBinding struct{}
+
+func (uriBinding) Name() string {
+	return "uri"
+}
+
+func (uriBinding) BindUri(m map[string][]string, obj interface{}) error {
+	if err := mapUri(obj, m); err != nil {
+		return err
+	}
+	return validate(obj)
+}

+ 33 - 0
binding/xml.go

@@ -0,0 +1,33 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import (
+	"bytes"
+	"encoding/xml"
+	"io"
+	"net/http"
+)
+
+type xmlBinding struct{}
+
+func (xmlBinding) Name() string {
+	return "xml"
+}
+
+func (xmlBinding) Bind(req *http.Request, obj interface{}) error {
+	return decodeXML(req.Body, obj)
+}
+
+func (xmlBinding) BindBody(body []byte, obj interface{}) error {
+	return decodeXML(bytes.NewReader(body), obj)
+}
+func decodeXML(r io.Reader, obj interface{}) error {
+	decoder := xml.NewDecoder(r)
+	if err := decoder.Decode(obj); err != nil {
+		return err
+	}
+	return validate(obj)
+}

+ 35 - 0
binding/yaml.go

@@ -0,0 +1,35 @@
+// Copyright 2018 Gin Core Team.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import (
+	"bytes"
+	"io"
+	"net/http"
+
+	"gopkg.in/yaml.v2"
+)
+
+type yamlBinding struct{}
+
+func (yamlBinding) Name() string {
+	return "yaml"
+}
+
+func (yamlBinding) Bind(req *http.Request, obj interface{}) error {
+	return decodeYAML(req.Body, obj)
+}
+
+func (yamlBinding) BindBody(body []byte, obj interface{}) error {
+	return decodeYAML(bytes.NewReader(body), obj)
+}
+
+func decodeYAML(r io.Reader, obj interface{}) error {
+	decoder := yaml.NewDecoder(r)
+	if err := decoder.Decode(obj); err != nil {
+		return err
+	}
+	return validate(obj)
+}

+ 39 - 0
cache/memory.go

@@ -0,0 +1,39 @@
+package cache
+
+import (
+	"sync"
+)
+
+var (
+	memCache sync.Map
+)
+
+// Load returns the value stored in the map for a key, or nil if no value is present. The ok result indicates whether value was found in the map.
+func Load(key interface{}) (value interface{}, ok bool) {
+	return memCache.Load(key)
+}
+
+// Delete deletes the value for a key.
+func Delete(key interface{}) {
+	memCache.Delete(key)
+}
+
+// LoadAndDelete deletes the value for a key, returning the previous value if any. The loaded result reports whether the key was present.
+func LoadAndDelete(key interface{}) (value interface{}, loaded bool) {
+	return memCache.LoadAndDelete(key)
+}
+
+// LoadOrStore returns the existing value for the key if present. Otherwise, it stores and returns the given value. The loaded result is true if the value was loaded, false if stored.
+func LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) {
+	return memCache.LoadOrStore(key, value)
+}
+
+// Range calls f sequentially for each key and value present in the map. If f returns false, range stops the iteration.
+func Range(f func(key, value interface{}) bool) {
+	memCache.Range(f)
+}
+
+// Store sets the value for a key.
+func Store(key, value interface{}) {
+	memCache.Store(key, value)
+}

+ 254 - 0
cache/redis.go

@@ -0,0 +1,254 @@
+package cache
+
+import (
+	"context"
+	"errors"
+	"time"
+
+	"github.com/go-redis/redis/v8"
+)
+
+var (
+	ctx          = context.Background()
+	defaultRedis *RedisCache
+)
+
+// RedisOptions options
+type RedisOptions struct {
+	DB       int
+	PoolSize int
+
+	DialTimeout  time.Duration
+	ReadTimeout  time.Duration
+	WriteTimeout time.Duration
+	PoolTimeout  time.Duration
+
+	Password string
+	Addr     string
+}
+
+// RedisClusterOptions cluster option
+type RedisClusterOptions struct {
+	DB         int
+	PoolSize   int
+	MaxRetries int
+
+	MinRetryBackoff time.Duration
+	MaxRetryBackoff time.Duration
+
+	DialTimeout  time.Duration
+	ReadTimeout  time.Duration
+	WriteTimeout time.Duration
+
+	PoolTimeout        time.Duration
+	IdleTimeout        time.Duration
+	IdleCheckFrequency time.Duration
+
+	Password string
+	Addrs    []string
+}
+
+// RedisCache define
+type RedisCache struct {
+	cluster bool
+	ps      *redis.PubSub
+	c       *redis.Client
+	cc      *redis.ClusterClient
+}
+
+// NewRedisCache new RedisCache object
+func NewRedisCache(opt RedisOptions) (rc *RedisCache, err error) {
+	rc = new(RedisCache)
+	c := redis.NewClient(&redis.Options{
+		Addr:         opt.Addr,
+		Password:     opt.Password,
+		DialTimeout:  opt.DialTimeout,
+		ReadTimeout:  opt.ReadTimeout,
+		WriteTimeout: opt.WriteTimeout,
+		PoolSize:     opt.PoolSize,
+		PoolTimeout:  opt.PoolTimeout,
+	})
+
+	_, err = c.Ping(ctx).Result()
+	rc.c = c
+	rc.cluster = false
+	return
+}
+
+// NewRedisClusterCache new RedisCluster object
+func NewRedisClusterCache(opt RedisClusterOptions) (rc *RedisCache, err error) {
+	rc = new(RedisCache)
+	var cfg redis.ClusterOptions
+
+	cfg.Addrs = opt.Addrs
+	cfg.Password = opt.Password
+
+	cfg.DialTimeout = opt.DialTimeout
+	cfg.ReadTimeout = opt.ReadTimeout
+	cfg.WriteTimeout = opt.WriteTimeout
+
+	cfg.PoolSize = opt.PoolSize
+	cfg.PoolTimeout = opt.PoolTimeout
+	cfg.IdleTimeout = opt.IdleTimeout
+	cfg.IdleCheckFrequency = opt.IdleCheckFrequency
+
+	c := redis.NewClusterClient(&cfg)
+
+	_, err = c.Ping(ctx).Result()
+	rc.cc = c
+	rc.cluster = true
+	return
+}
+
+// Get get value from cache
+func (c RedisCache) Get(k string) (string, error) {
+	if c.cluster {
+		return c.cc.Get(ctx, k).Result()
+	}
+	return c.c.Get(ctx, k).Result()
+}
+
+// Set key-value to cache
+func (c RedisCache) Set(k, v string, expiration time.Duration) (string, error) {
+	if c.cluster {
+		return c.cc.Set(ctx, k, v, expiration).Result()
+	}
+	return c.c.Set(ctx, k, v, expiration).Result()
+}
+
+// Del delete key from cache
+func (c RedisCache) Del(ks ...string) (int64, error) {
+	if c.cluster {
+		return c.cc.Del(ctx, ks...).Result()
+	}
+	return c.c.Del(ctx, ks...).Result()
+}
+
+// Publish posts the message to the channel.
+func (c RedisCache) Publish(channel string, message interface{}) (int64, error) {
+	if c.cluster {
+		return c.cc.Publish(ctx, channel, message).Result()
+	}
+	return c.c.Publish(ctx, channel, message).Result()
+}
+
+// Subscribe subscribes the client to the specified channels.
+func (c *RedisCache) Subscribe(channels ...string) error {
+	if c.cluster {
+		c.ps = c.cc.Subscribe(ctx, channels...)
+	} else {
+		c.ps = c.c.Subscribe(ctx, channels...)
+	}
+
+	return c.ps.Ping(ctx)
+}
+
+// CloseSubscribe close
+func (c *RedisCache) CloseSubscribe() error {
+	if c.ps == nil {
+		return nil
+	}
+	return c.ps.Close()
+}
+
+// Unsubscribe the client from the given channels, or from all of them if none is given.
+func (c *RedisCache) Unsubscribe(channels ...string) error {
+	if c.ps == nil {
+		return nil
+	}
+	return c.ps.Unsubscribe(ctx, channels...)
+}
+
+// ReceiveSubscribeMessage returns a Message or error ignoring Subscription and Pong messages. This is low-level API and in most cases Channel should be used instead.
+func (c *RedisCache) ReceiveSubscribeMessage() (channel, message string, err error) {
+	if c.ps == nil {
+		err = errors.New("not init subscribe")
+		return
+	}
+	var msg *redis.Message
+	msg, err = c.ps.ReceiveMessage(ctx)
+	if err != nil {
+		return
+	}
+	channel = msg.Channel
+	message = msg.Payload
+	return
+}
+
+// Info redis info
+func (c RedisCache) Info(section ...string) (string, error) {
+	if c.cluster {
+		return c.cc.Info(ctx, section...).Result()
+	}
+	return c.c.Info(ctx, section...).Result()
+}
+
+// ClusterInfo redis cluster info
+func (c RedisCache) ClusterInfo() (string, error) {
+	if c.cluster {
+		return c.cc.ClusterInfo(ctx).Result()
+	}
+	return c.c.ClusterInfo(ctx).Result()
+}
+
+// SetDefaultRedisOption set default option
+func SetDefaultRedisOption(opt RedisOptions) (err error) {
+	defaultRedis, err = NewRedisCache(opt)
+	return
+}
+
+// SetDefaultRedisClusterOption set default cluster option
+func SetDefaultRedisClusterOption(opt RedisClusterOptions) (err error) {
+	defaultRedis, err = NewRedisClusterCache(opt)
+	return
+}
+
+// RedisGet get value from cache
+func RedisGet(k string) (string, error) {
+	return defaultRedis.Get(k)
+}
+
+// RedisSet key-value to cache
+func RedisSet(k, v string, expiration time.Duration) (string, error) {
+	return defaultRedis.Set(k, v, expiration)
+}
+
+// RedisDel delete keys from cache
+func RedisDel(keys ...string) (int64, error) {
+	return defaultRedis.Del(keys...)
+}
+
+// RedisInfo redis info
+func RedisInfo(section ...string) (string, error) {
+	return defaultRedis.Info(section...)
+}
+
+// RedisClusterInfo redis cluster info
+func RedisClusterInfo() (string, error) {
+	return defaultRedis.ClusterInfo()
+}
+
+// Publish posts the message to the channel.
+func RedisPublish(channel string, message interface{}) (int64, error) {
+	return defaultRedis.Publish(channel, message)
+}
+
+// Subscribe subscribes the client to the specified channels.
+func RedisSubscribe(channels ...string) error {
+	return defaultRedis.Subscribe(channels...)
+}
+
+// RedisCloseSubscribe close
+func RedisCloseSubscribe() error {
+	return defaultRedis.CloseSubscribe()
+}
+
+// Unsubscribe the client from the given channels, or from all of them if none is given.
+func RedisUnsubscribe(channels ...string) error {
+	return defaultRedis.Unsubscribe(channels...)
+}
+
+// RedisReceiveSubscribeMessage returns a Message or error ignoring Subscription and Pong messages. This is low-level API and in most cases Channel should be used instead.
+func RedisReceiveSubscribeMessage() (channel, message string, err error) {
+	return defaultRedis.ReceiveSubscribeMessage()
+}

+ 94 - 0
crypto/aes.go

@@ -0,0 +1,94 @@
+package crypto
+
+import (
+	"bytes"
+	"crypto/aes"
+	"crypto/cipher"
+)
+
+// AES aes
+type AES struct {
+	key []byte
+	iv  []byte
+}
+
+var (
+	defaultAes AES
+)
+
+// NewAES new
+func NewAES(key, iv string) *AES {
+	return &AES{
+		key: []byte(key),
+		iv:  []byte(iv),
+	}
+}
+
+// SetKeyIV set key & iv
+func (c *AES) SetKeyIV(key, iv string) {
+	c.key = []byte(key)
+	c.iv = []byte(iv)
+}
+
+// Encrypt encrypt data
+func (c *AES) Encrypt(bs []byte) (crypted []byte, err error) {
+	block, err := aes.NewCipher(c.key)
+	if err != nil {
+		return nil, err
+	}
+	blockSize := block.BlockSize()
+	bs = pkcs7Padding(bs, blockSize)
+
+	blockMode := cipher.NewCBCEncrypter(block, c.iv[:blockSize])
+	crypted = make([]byte, len(bs))
+
+	blockMode.CryptBlocks(crypted, bs)
+	return
+}
+
+// Decrypt decrypt data
+func (c *AES) Decrypt(bs []byte) (ds []byte, err error) {
+	block, err := aes.NewCipher(c.key)
+	if err != nil {
+		return nil, err
+	}
+	blockSize := block.BlockSize()
+	blockMode := cipher.NewCBCDecrypter(block, c.iv[:blockSize])
+	ds = make([]byte, len(bs))
+	blockMode.CryptBlocks(ds, bs)
+	ds = pkcs7UnPadding(ds)
+	return
+}
+
+func pkcs7Padding(cipherText []byte, blockSize int) []byte {
+	padding := blockSize - len(cipherText)%blockSize
+	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
+	return append(cipherText, padtext...)
+}
+
+func pkcs7UnPadding(data []byte) []byte {
+	length := len(data)
+	unpadding := int(data[length-1])
+	if unpadding >= length {
+		return []byte(``)
+	}
+	return data[:(length - unpadding)]
+}
+
+// SetAESDefaultKeyIV set default key & iv
+func SetAESDefaultKeyIV(key, iv string) {
+	defaultAes = AES{
+		key: []byte(key),
+		iv:  []byte(iv),
+	}
+}
+
+// AesEncrypt default key & iv encrypt
+func AesEncrypt(bs []byte) ([]byte, error) {
+	return defaultAes.Encrypt(bs)
+}
+
+// AesDecrypt default key & iv decrypt
+func AesDecrypt(bs []byte) ([]byte, error) {
+	return defaultAes.Decrypt(bs)
+}

+ 8 - 0
db/const.go

@@ -0,0 +1,8 @@
+package db
+
+const (
+	// DriverPostgres postgres
+	DriverPostgres = `postgres`
+	// DriverMySQL MySQL
+	DriverMySQL = `mysql`
+)

+ 1 - 0
db/driver.go

@@ -0,0 +1 @@
+package db

+ 14 - 0
db/option.go

@@ -0,0 +1,14 @@
+package db
+
+import (
+	"time"
+)
+
+// Option option
+type Option struct {
+	Driver       string
+	DNS          string
+	MaxOpenConns int
+	MaxIdle      int
+	MaxLifetime  time.Duration
+}

+ 353 - 0
db/sqlx.go

@@ -0,0 +1,353 @@
+package db
+
+import (
+	"database/sql"
+	"fmt"
+	"sync"
+
+	// PostgreSQL
+	_ "github.com/lib/pq"
+	// MySQL
+	_ "github.com/go-sql-driver/mysql"
+	"github.com/jmoiron/sqlx"
+)
+
+// DB db
+type DB struct {
+	Driver string
+	c      *sqlx.DB
+	tx     *sqlx.Tx
+}
+
+var (
+	defaultConfig Option
+	defaultDb     *DB
+	db            *sqlx.DB
+	once          sync.Once
+)
+
+// SetDefaultOption default connect option
+func SetDefaultOption(opt Option) (err error) {
+	defaultConfig = opt
+	defaultDb = &DB{Driver: opt.Driver}
+	defaultDb.c, err = connect()
+	return
+}
+
+// ReleaseDefault release default connect
+func ReleaseDefault() error {
+	if defaultDb != nil {
+		if defaultDb.c != nil {
+			defaultDb.c.Close()
+		}
+	}
+	return nil
+}
+
+// New new DB dynamic object
+func New(opt Option) (dbx *DB, err error) {
+	//dbx = &DB{}
+	dbx.Driver = opt.Driver
+	dbx.c, err = sqlx.Connect(opt.Driver, opt.DNS)
+	if err != nil {
+		return
+	}
+
+	dbx.c.SetMaxOpenConns(opt.MaxOpenConns)
+	dbx.c.SetMaxIdleConns(opt.MaxIdle)
+	dbx.c.SetConnMaxLifetime(opt.MaxLifetime)
+	err = dbx.c.Ping()
+	return
+}
+
+// Release release connect
+func Release(dbx *DB) (err error) {
+	if dbx.c != nil {
+		err = dbx.c.Close()
+	}
+	return
+}
+
+func connect() (dbx *sqlx.DB, err error) {
+	once.Do(func() {
+		db, err = sqlx.Connect(defaultConfig.Driver, defaultConfig.DNS)
+		if err == nil {
+			db.DB.SetMaxOpenConns(defaultConfig.MaxOpenConns)
+			db.DB.SetMaxIdleConns(defaultConfig.MaxIdle)
+			db.DB.SetConnMaxLifetime(defaultConfig.MaxLifetime)
+			err = db.Ping()
+		}
+	})
+
+	dbx = db
+	return
+}
+
+// Connect connect to database
+func (d *DB) Connect() (err error) {
+	if d.c != nil {
+		return
+	}
+
+	d.c, err = connect()
+	return
+}
+
+// Close close database connect
+func (d *DB) Close() {
+	// use pool
+	//d.c.Close()
+}
+
+// BeginTrans begin trans
+func (d *DB) BeginTrans() (err error) {
+	d.c, err = connect()
+	if err != nil {
+		return
+	}
+
+	d.tx = d.c.MustBegin()
+	return
+}
+
+// Commit commit
+func (d *DB) Commit() error {
+	return d.tx.Commit()
+}
+
+// Rollback rollback
+func (d *DB) Rollback() error {
+	return d.tx.Rollback()
+}
+
+// TransNamedExec trans execute
+func (d *DB) TransExec(query string, args ...interface{}) (LastInsertId, RowsAffected int64, err error) {
+	rs, err := d.tx.Exec(query, args...)
+	if err != nil {
+		return
+	}
+
+	RowsAffected, _ = rs.RowsAffected()
+	LastInsertId, _ = rs.LastInsertId()
+	return
+}
+
+// TransNamedExec trans execute, named bindvars
+func (d *DB) TransNamedExec(query string, args interface{}) (LastInsertId, RowsAffected int64, err error) {
+	rs, err := d.tx.NamedExec(query, args)
+	if err != nil {
+		return
+	}
+
+	RowsAffected, _ = rs.RowsAffected()
+	LastInsertId, _ = rs.LastInsertId()
+	return
+}
+
+// TransGet trans get row
+func (d *DB) TransGet(dest interface{}, query string, args ...interface{}) (err error) {
+	d.tx.Get(dest, query, args...)
+	return
+}
+
+// TransNamedGet trans get row, named bindvars
+func (d *DB) TransNamedGet(dest interface{}, query string, args interface{}) (err error) {
+	var nstmt *sqlx.NamedStmt
+	nstmt, err = d.tx.PrepareNamed(query)
+	if err != nil {
+		return
+	}
+	defer nstmt.Close()
+
+	err = nstmt.Get(dest, args)
+	return
+}
+
+// TransSelect trans get rows
+func (d *DB) TransSelect(dest interface{}, query string, args ...interface{}) (err error) {
+	d.tx.Select(dest, query, args...)
+	return
+}
+
+// Get get one
+func (d *DB) Get(dest interface{}, query string, args ...interface{}) (err error) {
+	err = d.Connect()
+	if err != nil {
+		return
+	}
+	defer d.Close()
+
+	err = d.c.Get(dest, query, args...)
+	return
+}
+
+// Get get one, named bindvars
+func (d *DB) NamedGet(dest interface{}, query string, args interface{}) (err error) {
+	err = d.Connect()
+	if err != nil {
+		return
+	}
+	defer d.Close()
+
+	nstmt, err := d.c.PrepareNamed(query)
+	if err != nil {
+		return
+	}
+	defer nstmt.Close()
+
+	err = nstmt.Get(dest, args)
+	return
+}
+
+// Select select rows
+func (d *DB) Select(dest interface{}, query string, args ...interface{}) error {
+	err := d.Connect()
+	if err != nil {
+		return err
+	}
+	defer d.Close()
+
+	err = d.c.Select(dest, query, args...)
+	return err
+}
+
+// NamedSelect select rows, named bindvars
+func (d *DB) NamedSelect(dest interface{}, query string, args interface{}) (err error) {
+	err = d.Connect()
+	if err != nil {
+		return err
+	}
+	defer d.Close()
+
+	nstmt, err := d.c.PrepareNamed(query)
+	if err != nil {
+		return err
+	}
+	defer nstmt.Close()
+
+	err = nstmt.Select(dest, args)
+	return err
+}
+
+// Exec exec
+func (d *DB) Exec(query string, args ...interface{}) (LastInsertId, RowsAffected int64, err error) {
+	err = d.Connect()
+	if err != nil {
+		return
+	}
+	defer d.Close()
+
+	var rs sql.Result
+	rs, err = d.c.Exec(query, args...)
+	if err != nil {
+		return
+	}
+
+	LastInsertId, _ = rs.LastInsertId()
+	RowsAffected, _ = rs.RowsAffected()
+	return
+}
+
+// NamedExec exec, named bindvars
+func (d *DB) NamedExec(query string, args interface{}) (LastInsertId, RowsAffected int64, err error) {
+	err = d.Connect()
+	if err != nil {
+		return
+	}
+	defer d.Close()
+
+	var rs sql.Result
+	rs, err = d.c.NamedExec(query, args)
+	if err != nil {
+		return
+	}
+
+	LastInsertId, _ = rs.LastInsertId()
+	RowsAffected, _ = rs.RowsAffected()
+	return
+}
+
+// Limit MySQL/PostgreSQL limit
+func (d *DB) Limit(page, pagesize int) string {
+	// MySQL limit n, size
+	if d.Driver == DriverMySQL {
+		return fmt.Sprintf(" LIMIT %d, %d", (page-1)*pagesize, pagesize)
+	}
+	// // PostgreSQL limit size offset n
+	return fmt.Sprintf(" LIMIT %d OFFSET %d", pagesize, (page-1)*pagesize)
+}
+
+// BeginTrans begin trans
+func BeginTrans() (err error) {
+	return defaultDb.BeginTrans()
+}
+
+// Commit commit
+func Commit() error {
+	return defaultDb.Commit()
+}
+
+// Rollback rollback
+func Rollback() error {
+	return defaultDb.Rollback()
+}
+
+// TransNamedExec trans execute
+func TransExec(query string, args ...interface{}) (LastInsertId, RowsAffected int64, err error) {
+	return defaultDb.TransExec(query, args...)
+}
+
+// TransNamedExec trans execute, named bindvars
+func TransNamedExec(query string, args interface{}) (LastInsertId, RowsAffected int64, err error) {
+	return defaultDb.TransNamedExec(query, args)
+}
+
+// TransGet trans get row
+func TransGet(dest interface{}, query string, args interface{}) (err error) {
+	return defaultDb.TransGet(dest, query, args)
+}
+
+// TransNamedGet trans get row, named bindvars
+func TransNamedGet(dest interface{}, query string, args interface{}) (err error) {
+	return defaultDb.TransNamedGet(dest, query, args)
+}
+
+// TransSelect trans get rows
+func TransSelect(dest interface{}, query string, args ...interface{}) (err error) {
+	return defaultDb.TransSelect(dest, query, args...)
+}
+
+// Get get one
+func Get(dest interface{}, query string, args ...interface{}) error {
+	return defaultDb.Get(dest, query, args...)
+}
+
+// Get get one, named bindvars
+func NamedGet(dest interface{}, query string, args interface{}) (err error) {
+	return defaultDb.NamedGet(dest, query, args)
+}
+
+// Select select rows
+func Select(dest interface{}, query string, args ...interface{}) error {
+	return defaultDb.Select(dest, query, args...)
+}
+
+// NamedSelect select rows, named bindvars
+func NamedSelect(dest interface{}, query string, args interface{}) (err error) {
+	return defaultDb.NamedSelect(dest, query, args)
+}
+
+// Exec execute
+func Exec(query string, args ...interface{}) (LastInsertId, RowsAffected int64, err error) {
+	return defaultDb.Exec(query, args...)
+}
+
+// NamedExec exec, named bindvars
+func NamedExec(query string, args interface{}) (LastInsertId, RowsAffected int64, err error) {
+	return defaultDb.NamedExec(query, args)
+}
+
+// Limit MySQL/PostgreSQL limit
+func Limit(page, pagesize int) string {
+	return defaultDb.Limit(page, pagesize)
+}

+ 14 - 0
go.mod

@@ -0,0 +1,14 @@
+module git.chuangxin1.com/myth/sacred
+
+go 1.16
+
+require (
+	github.com/go-redis/redis/v8 v8.11.0
+	github.com/go-sql-driver/mysql v1.6.0
+	github.com/golang/protobuf v1.5.2
+	github.com/jmoiron/sqlx v1.3.4
+	github.com/json-iterator/go v1.1.11
+	github.com/lib/pq v1.10.2
+	github.com/ugorji/go/codec v1.2.6
+	gopkg.in/yaml.v2 v2.4.0
+)

+ 119 - 0
go.sum

@@ -0,0 +1,119 @@
+github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/go-redis/redis/v8 v8.11.0 h1:O1Td0mQ8UFChQ3N9zFQqo6kTU2cJ+/it88gDB+zg0wo=
+github.com/go-redis/redis/v8 v8.11.0/go.mod h1:DLomh7y2e3ggQXQLd1YgmvIfecPJoFl7WU5SOQ/r06M=
+github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w=
+github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
+github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
+github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
+github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
+github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
+github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E=
+github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
+github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
+github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
+golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

+ 53 - 0
hash/hmac.go

@@ -0,0 +1,53 @@
+package hash
+
+import (
+	"crypto/hmac"
+	"crypto/md5"
+	"crypto/sha1"
+	"crypto/sha256"
+	"crypto/sha512"
+)
+
+// HmacMD5 hmac md5 hash
+func HmacMD5(k, bs []byte) (hs []byte, err error) {
+	h := hmac.New(md5.New, k)
+	_, err = h.Write(bs)
+	if err != nil {
+		return
+	}
+	hs = h.Sum(nil)
+	return
+}
+
+// HmacSHA1 hmac sha1 hash
+func HmacSHA1(k, bs []byte) (hs []byte, err error) {
+	h := hmac.New(sha1.New, k)
+	_, err = h.Write(bs)
+	if err != nil {
+		return
+	}
+	hs = h.Sum(nil)
+	return
+}
+
+// HmacSHA256 hmac sha256 hash
+func HmacSHA256(k, bs []byte) (hs []byte, err error) {
+	h := hmac.New(sha256.New, k)
+	_, err = h.Write(bs)
+	if err != nil {
+		return
+	}
+	hs = h.Sum(nil)
+	return
+}
+
+// HmacSHA512 hmac sha256 hash
+func HmacSHA512(k, bs []byte) (hs []byte, err error) {
+	h := hmac.New(sha512.New, k)
+	_, err = h.Write(bs)
+	if err != nil {
+		return
+	}
+	hs = h.Sum(nil)
+	return
+}

+ 16 - 0
hash/md5.go

@@ -0,0 +1,16 @@
+package hash
+
+import (
+	"crypto/md5"
+)
+
+func MD5(bs []byte) (hs []byte, err error) {
+	h := md5.New()
+	_, err = h.Write(bs)
+	if err != nil {
+		return
+	}
+
+	hs = h.Sum(nil)
+	return
+}

+ 43 - 0
hash/sha.go

@@ -0,0 +1,43 @@
+package hash
+
+import (
+	"crypto/sha1"
+	"crypto/sha256"
+	"crypto/sha512"
+)
+
+// SHA1 sha1 hash
+func SHA1(bs []byte) (hs []byte, err error) {
+	h := sha1.New()
+	_, err = h.Write(bs)
+	if err != nil {
+		return
+	}
+
+	hs = h.Sum(nil)
+	return
+}
+
+// SHA256 sha256 hash
+func SHA256(bs []byte) (hs []byte, err error) {
+	h := sha256.New()
+	_, err = h.Write(bs)
+	if err != nil {
+		return
+	}
+
+	hs = h.Sum(nil)
+	return
+}
+
+// SHA512 sha512 hash
+func SHA512(bs []byte) (hs []byte, err error) {
+	h := sha512.New()
+	_, err = h.Write(bs)
+	if err != nil {
+		return
+	}
+
+	hs = h.Sum(nil)
+	return
+}

+ 130 - 0
http/client.go

@@ -0,0 +1,130 @@
+package http
+
+import (
+	"compress/gzip"
+	"crypto/tls"
+	"crypto/x509"
+	"io"
+	"net"
+	"net/http"
+	"time"
+)
+
+// readBody read response
+func readBody(res *http.Response) (msg Message, err error) {
+	var (
+		body   []byte
+		reader io.Reader
+	)
+
+	encoding := res.Header.Get("Content-Encoding")
+	switch encoding {
+	case "gzip":
+		reader, err = gzip.NewReader(res.Body)
+		if err == nil {
+			body, err = io.ReadAll(reader)
+		}
+	default:
+		body, err = io.ReadAll(res.Body)
+	}
+	if err != nil {
+		return
+	}
+
+	msg.StatusCode = res.StatusCode
+	msg.Header = res.Header
+	msg.Body = body
+	return
+}
+
+// newRequest new request
+func newRequest(method, uri string, body io.Reader, opt RequestOption) (res *http.Response, err error) {
+	t := &http.Transport{
+		Dial: func(netw, addr string) (net.Conn, error) {
+			conn, err := net.DialTimeout(netw, addr, opt.RequestTimeOut)
+			if err != nil {
+				return nil, err
+			}
+			conn.SetDeadline(time.Now().Add(opt.RequestTimeOut))
+			return conn, nil
+		},
+		ResponseHeaderTimeout: time.Second * opt.ResponseHeaderTimeout,
+	}
+	if len(opt.CertFile) > 0 {
+		cert, e := tls.LoadX509KeyPair(opt.CertFile, opt.KeyFile)
+		if e != nil {
+			t.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+		} else {
+			pool := x509.NewCertPool()
+			t.TLSClientConfig = &tls.Config{InsecureSkipVerify: true, Certificates: []tls.Certificate{cert}, RootCAs: pool}
+		}
+	}
+	c := &http.Client{Transport: t}
+	var req *http.Request
+	req, err = http.NewRequest(method, uri, body)
+	if err != nil {
+		return
+	}
+
+	for k, v := range opt.Headers {
+		req.Header.Add(k, v)
+	}
+
+	res, err = c.Do(req)
+	return
+}
+
+// Get HTTP request GET
+func Get(uri string, opt RequestOption) (msg Message, err error) {
+	var res *http.Response
+	if res, err = newRequest("GET", uri, nil, opt); err != nil {
+		return
+	}
+
+	defer res.Body.Close()
+	msg, err = readBody(res)
+	return
+}
+
+// GetJSON HTTP request GET, response JSON
+func GetJSON(v interface{}, uri string, opt RequestOption) (msg Message, err error) {
+	var res *http.Response
+	if res, err = newRequest("GET", uri, nil, opt); err != nil {
+		return
+	}
+
+	defer res.Body.Close()
+	msg, err = readBody(res)
+	if err != nil {
+		return
+	}
+	err = msg.JSON(v)
+	return
+}
+
+// GetXML HTTP request GET, response XML
+func GetXML(v interface{}, uri string, opt RequestOption) (msg Message, err error) {
+	var res *http.Response
+	if res, err = newRequest("GET", uri, nil, opt); err != nil {
+		return
+	}
+
+	defer res.Body.Close()
+	msg, err = readBody(res)
+	if err != nil {
+		return
+	}
+	err = msg.XML(v)
+	return
+}
+
+// Post HTTP request POST
+func Post(uri string, body io.Reader, opt RequestOption) (msg Message, err error) {
+	var res *http.Response
+	if res, err = newRequest("POST", uri, body, opt); err != nil {
+		return
+	}
+	defer res.Body.Close()
+	msg, err = readBody(res)
+	return
+}

+ 30 - 0
http/const.go

@@ -0,0 +1,30 @@
+package http
+
+import (
+	"time"
+)
+
+// const
+const (
+	// ContentType header Content-Type
+	ContentType = `Content-Type`
+	// AcceptEncoding header Accept-Encoding
+	AcceptEncoding = `Accept-Encoding`
+
+	// ContentTypeURL application/x-www-form-urlencoded
+	ContentTypeURL = `application/x-www-form-urlencoded; charset=utf-8`
+	// ContentTypeJSON application/json
+	ContentTypeJSON = `application/json; charset=utf-8`
+	// ContentTypeXML application/xml
+	ContentTypeXML = `application/xml; charset=utf-8`
+	// ContentTypeMultipart multipart/form-data
+	ContentTypeMultipart = `multipart/form-data`
+)
+
+var (
+	readTimeout       = 30 * time.Second
+	readHeaderTimeout = 30 * time.Second
+	writeTimeout      = 30 * time.Second
+	idleTimeout       = 30 * time.Second
+	shutTimeout       = 30 * time.Second
+)

+ 17 - 0
http/middleware/value.go

@@ -0,0 +1,17 @@
+package middleware
+
+import (
+	"context"
+	"net/http"
+)
+
+// WithValue set k/v in a context chain.
+func WithValue(k, v interface{}) func(next http.Handler) http.Handler {
+	return func(next http.Handler) http.Handler {
+		f := func(w http.ResponseWriter, r *http.Request) {
+			r = r.WithContext(context.WithValue(r.Context(), k, v))
+			next.ServeHTTP(w, r)
+		}
+		return http.HandlerFunc(f)
+	}
+}

+ 151 - 0
http/option.go

@@ -0,0 +1,151 @@
+package http
+
+import (
+	"time"
+)
+
+// RequestOption HTTP request option
+type RequestOption struct {
+	RequestTimeOut        time.Duration
+	ResponseHeaderTimeout time.Duration
+
+	Headers map[string]string
+
+	CertFile string
+	KeyFile  string
+}
+
+// ServerOption serve option
+type ServerOption struct {
+	CertFile string
+	KeyFile  string
+
+	// ReadHeaderTimeout is the amount of time allowed to read request headers
+	ReadHeaderTimeout time.Duration
+	// ReadTimeout is the maximum duration for reading the entire
+	// request, including the body.
+	ReadTimeout time.Duration
+	// WriteTimeout is the maximum duration before timing out
+	// writes of the response.
+	WriteTimeout time.Duration
+	// IdleTimeout is the maximum amount of time to wait for the
+	// next request when keep-alives are enabled.
+	IdleTimeout time.Duration
+
+	// MaxHeaderBytes controls the maximum number of bytes the
+	// server will read parsing the request header's keys and
+	// values, including the request line. It does not limit the
+	// size of the request body.
+	// If zero, DefaultMaxHeaderBytes is used.
+	MaxHeaderBytes int
+
+	ShutdownTimeout time.Duration
+}
+
+// DefaultRequestOption default request option
+func DefaultRequestOption() RequestOption {
+	return RequestOption{
+		Headers:               map[string]string{},
+		RequestTimeOut:        60 * time.Second,
+		ResponseHeaderTimeout: 60 * time.Second,
+	}
+}
+
+// SetTimeOut set request timeout
+func (r *RequestOption) SetTimeOut(v time.Duration) {
+	r.RequestTimeOut = v
+}
+
+// SetResponseHeaderTimeout set response header timeout
+func (r *RequestOption) SetResponseHeaderTimeout(v time.Duration) {
+	r.ResponseHeaderTimeout = v
+}
+
+// SetCert set request cert & key file
+func (r *RequestOption) SetCert(certFile, keyFile string) {
+	r.CertFile = certFile
+	r.KeyFile = keyFile
+}
+
+// SetHeader set header
+func (r *RequestOption) SetHeader(k, v string) {
+	r.Headers[k] = v
+}
+
+// AcceptEncodingGZIP Accept-Encoding gzip
+func (r *RequestOption) AcceptEncodingGZIP() {
+	r.Headers[AcceptEncoding] = "gzip, deflate, br"
+}
+
+// SetContentTypeMultipart Content-Type multipart
+func (r *RequestOption) SetContentTypeMultipart(v string) {
+	r.Headers[ContentType] = v
+}
+
+// SetContentTypeJSON Content-Type json
+func (r *RequestOption) SetContentTypeJSON() {
+	r.Headers[ContentType] = ContentTypeJSON
+}
+
+// SetContentTypeJSON Content-Type json
+func (r *RequestOption) SetContentTypeURL() {
+	r.Headers[ContentType] = ContentTypeURL
+}
+
+// SetContentTypeJSON Content-Type json
+func (r *RequestOption) SetContentTypeXML() {
+	r.Headers[ContentType] = ContentTypeXML
+}
+
+func DefaultServerOption() ServerOption {
+	return ServerOption{
+		ReadTimeout:       readTimeout,
+		ReadHeaderTimeout: readHeaderTimeout,
+		WriteTimeout:      writeTimeout,
+		IdleTimeout:       idleTimeout,
+
+		MaxHeaderBytes: 1 << 20,
+
+		ShutdownTimeout: shutTimeout,
+	}
+}
+
+// SetReadTimeout set ReadTimeout
+func (s *ServerOption) SetReadTimeout(t time.Duration) {
+	s.ReadTimeout = t
+}
+
+// SetReadHeaderTimeout set ReadHeaderTimeout
+func (s *ServerOption) SetReadHeaderTimeout(t time.Duration) {
+	s.ReadHeaderTimeout = t
+}
+
+// SetWriteTimeout set WriteTimeout
+func (s *ServerOption) SetWriteTimeout(t time.Duration) {
+	s.WriteTimeout = t
+}
+
+// SetIdleTimeout set IdleTimeout
+func (s *ServerOption) SetIdleTimeout(t time.Duration) {
+	s.IdleTimeout = t
+}
+
+// SetMaxHeaderBytes set MaxHeaderBytes
+func (s *ServerOption) SetMaxHeaderBytes(n int) {
+	s.MaxHeaderBytes = n
+}
+
+// SetShutdownTimeout set ShutdownTimeout
+func (s *ServerOption) SetShutdownTimeout(t time.Duration) {
+	s.ShutdownTimeout = t
+}
+
+// SetCertFile set CertFile
+func (s *ServerOption) SetCertFile(path string) {
+	s.CertFile = path
+}
+
+// SetKeyFile set KeyFile
+func (s *ServerOption) SetKeyFile(path string) {
+	s.KeyFile = path
+}

+ 24 - 0
http/response.go

@@ -0,0 +1,24 @@
+package http
+
+import (
+	"encoding/json"
+	"encoding/xml"
+	"net/http"
+)
+
+// Message HTTP response
+type Message struct {
+	StatusCode int
+	Body       []byte
+	Header     http.Header
+}
+
+// JSON Body to JSON
+func (m Message) JSON(v interface{}) error {
+	return json.Unmarshal(m.Body, v)
+}
+
+// XML Body to XML
+func (m Message) XML(v interface{}) error {
+	return xml.Unmarshal(m.Body, v)
+}

+ 59 - 0
http/serve.go

@@ -0,0 +1,59 @@
+package http
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"os"
+	"os/signal"
+	"syscall"
+	//"golang.org/x/net/http2"
+)
+
+func run(addr string, router http.Handler, option ServerOption) (errs, err error) {
+	s := &http.Server{
+		Addr:              addr,
+		Handler:           router,
+		ReadTimeout:       option.ReadTimeout,
+		ReadHeaderTimeout: option.ReadHeaderTimeout,
+		WriteTimeout:      option.WriteTimeout,
+		IdleTimeout:       option.IdleTimeout,
+		MaxHeaderBytes:    option.MaxHeaderBytes,
+	}
+
+	errc := make(chan error)
+	go func() {
+		c := make(chan os.Signal, 1)
+		signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
+		errc <- fmt.Errorf("%s", <-c)
+	}()
+
+	go func() {
+		if len(option.CertFile) > 0 && len(option.KeyFile) > 0 {
+			/*
+				http2.VerboseLogs = false
+				http2.ConfigureServer(s, nil)
+				// */
+			errs = s.ListenAndServeTLS(option.CertFile, option.KeyFile)
+		} else {
+			errs = s.ListenAndServe()
+		}
+		errc <- errs
+	}()
+
+	err = <-errc
+	ctx, cancel := context.WithTimeout(context.Background(), option.ShutdownTimeout)
+	defer cancel()
+	s.Shutdown(ctx)
+	return
+}
+
+// ListenAndServe new server and start
+func ListenAndServe(addr string, router http.Handler, option ServerOption) (es, e error) {
+	return run(addr, router, option)
+}
+
+// ListenAndServeV2 new http2 server and start
+func ListenAndServeV2(addr string, router http.Handler, option ServerOption) (es, e error) {
+	return run(addr, router, option)
+}

+ 21 - 0
internal/json/json.go

@@ -0,0 +1,21 @@
+// Copyright 2021 ls. All rights reserved.
+
+//go:build !jsoniter
+// +build !jsoniter
+
+package json
+
+import "encoding/json"
+
+var (
+	// Marshal is exported by sacred/json package.
+	Marshal = json.Marshal
+	// Unmarshal is exported by sacred/json package.
+	Unmarshal = json.Unmarshal
+	// MarshalIndent is exported by sacred/json package.
+	MarshalIndent = json.MarshalIndent
+	// NewDecoder is exported by sacred/json package.
+	NewDecoder = json.NewDecoder
+	// NewEncoder is exported by sacred/json package.
+	NewEncoder = json.NewEncoder
+)

+ 22 - 0
internal/json/jsoniter.go

@@ -0,0 +1,22 @@
+// Copyright 2021 ls. All rights reserved.
+
+//go:build jsoniter
+// +build jsoniter
+
+package json
+
+import jsoniter "github.com/json-iterator/go"
+
+var (
+	json = jsoniter.ConfigCompatibleWithStandardLibrary
+	// Marshal is exported by sacred/json package.
+	Marshal = json.Marshal
+	// Unmarshal is exported by sacred/json package.
+	Unmarshal = json.Unmarshal
+	// MarshalIndent is exported by sacred/json package.
+	MarshalIndent = json.MarshalIndent
+	// NewDecoder is exported by sacred/json package.
+	NewDecoder = json.NewDecoder
+	// NewEncoder is exported by sacred/json package.
+	NewEncoder = json.NewEncoder
+)

+ 8 - 0
sys/limit_darwin.go

@@ -0,0 +1,8 @@
+// +build darwin
+
+package sys
+
+// SetLimit set NOFILE
+func SetLimit(max uint64) error {
+	return nil
+}

+ 26 - 0
sys/limit_unix.go

@@ -0,0 +1,26 @@
+// +build aix dragonfly freebsd linux netbsd openbsd solaris
+
+package sys
+
+import (
+	"syscall"
+)
+
+// SetLimit set NOFILE
+func SetLimit(max uint64) error {
+	var rlimit syscall.Rlimit
+
+	err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit)
+	if err != nil {
+		return err
+	}
+
+	rlimit.Cur = max
+	rlimit.Max = max
+	err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlimit)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}

+ 8 - 0
sys/limit_windows.go

@@ -0,0 +1,8 @@
+// +build windows
+
+package sys
+
+// SetLimit set NOFILE
+func SetLimit(max uint64) error {
+	return nil
+}

+ 57 - 0
types/datetime.go

@@ -0,0 +1,57 @@
+package types
+
+import (
+	"database/sql/driver"
+	"errors"
+	"time"
+)
+
+// TimeText time text
+type TimeText string
+
+// DateText date text
+type DateText string
+
+// const
+const (
+	timeFormat = `2006-01-02 15:04:05`
+	dateFormat = `2006-01-02`
+)
+
+// Value implements the driver.Valuer interface
+func (t TimeText) Value() (driver.Value, error) {
+	t1, err := time.ParseInLocation(timeFormat, string(t), time.Local)
+	if err != nil {
+		return nil, err
+	}
+	return TimeText(t1.Format(timeFormat)), nil
+}
+
+// Scan implements the sql.Scanner interface
+func (t *TimeText) Scan(src interface{}) error {
+	v, ok := src.(time.Time)
+	if !ok {
+		return errors.New("bad time.Time type assertion")
+	}
+	*t = TimeText(v.Format(timeFormat))
+	return nil
+}
+
+// Value implements the driver.Valuer interface
+func (t DateText) Value() (driver.Value, error) {
+	t1, err := time.ParseInLocation(dateFormat, string(t), time.Local)
+	if err != nil {
+		return nil, err
+	}
+	return DateText(t1.Format(dateFormat)), nil
+}
+
+// Scan implements the sql.Scanner interface
+func (t *DateText) Scan(src interface{}) error {
+	v, ok := src.(time.Time)
+	if !ok {
+		return errors.New("bad time.Time type assertion")
+	}
+	*t = DateText(v.Format(dateFormat))
+	return nil
+}

+ 171 - 0
types/types.go

@@ -0,0 +1,171 @@
+package types
+
+import (
+	"bytes"
+	"compress/gzip"
+	"database/sql/driver"
+	"encoding/json"
+	"errors"
+
+	"io/ioutil"
+)
+
+// GzippedText is a []byte which transparently gzips data being submitted to
+// a database and ungzips data being Scanned from a database.
+type GzippedText []byte
+
+// Value implements the driver.Valuer interface, gzipping the raw value of
+// this GzippedText.
+func (g GzippedText) Value() (driver.Value, error) {
+	b := make([]byte, 0, len(g))
+	buf := bytes.NewBuffer(b)
+	w := gzip.NewWriter(buf)
+	w.Write(g)
+	w.Close()
+	return buf.Bytes(), nil
+}
+
+// Scan implements the sql.Scanner interface, ungzipping the value coming off
+// the wire and storing the raw result in the GzippedText.
+func (g *GzippedText) Scan(src interface{}) error {
+	var source []byte
+	switch src := src.(type) {
+	case string:
+		source = []byte(src)
+	case []byte:
+		source = src
+	default:
+		return errors.New("Incompatible type for GzippedText")
+	}
+	reader, err := gzip.NewReader(bytes.NewReader(source))
+	if err != nil {
+		return err
+	}
+	defer reader.Close()
+	b, err := ioutil.ReadAll(reader)
+	if err != nil {
+		return err
+	}
+	*g = GzippedText(b)
+	return nil
+}
+
+// JSONText is a json.RawMessage, which is a []byte underneath.
+// Value() validates the json format in the source, and returns an error if
+// the json is not valid.  Scan does no validation.  JSONText additionally
+// implements `Unmarshal`, which unmarshals the json within to an interface{}
+type JSONText json.RawMessage
+
+var emptyJSON = JSONText("{}")
+
+// MarshalJSON returns the *j as the JSON encoding of j.
+func (j JSONText) MarshalJSON() ([]byte, error) {
+	if len(j) == 0 {
+		return emptyJSON, nil
+	}
+	return j, nil
+}
+
+// UnmarshalJSON sets *j to a copy of data
+func (j *JSONText) UnmarshalJSON(data []byte) error {
+	if j == nil {
+		return errors.New("JSONText: UnmarshalJSON on nil pointer")
+	}
+	*j = append((*j)[0:0], data...)
+	return nil
+}
+
+// Value returns j as a value.  This does a validating unmarshal into another
+// RawMessage.  If j is invalid json, it returns an error.
+func (j JSONText) Value() (driver.Value, error) {
+	var m json.RawMessage
+	var err = j.Unmarshal(&m)
+	if err != nil {
+		return []byte{}, err
+	}
+	return []byte(j), nil
+}
+
+// Scan stores the src in *j.  No validation is done.
+func (j *JSONText) Scan(src interface{}) error {
+	var source []byte
+	switch t := src.(type) {
+	case string:
+		source = []byte(t)
+	case []byte:
+		if len(t) == 0 {
+			source = emptyJSON
+		} else {
+			source = t
+		}
+	case nil:
+		*j = emptyJSON
+	default:
+		return errors.New("Incompatible type for JSONText")
+	}
+	*j = append((*j)[0:0], source...)
+	return nil
+}
+
+// Unmarshal unmarshal's the json in j to v, as in json.Unmarshal.
+func (j *JSONText) Unmarshal(v interface{}) error {
+	if len(*j) == 0 {
+		*j = emptyJSON
+	}
+	return json.Unmarshal([]byte(*j), v)
+}
+
+// String supports pretty printing for JSONText types.
+func (j JSONText) String() string {
+	return string(j)
+}
+
+// NullJSONText represents a JSONText that may be null.
+// NullJSONText implements the scanner interface so
+// it can be used as a scan destination, similar to NullString.
+type NullJSONText struct {
+	JSONText
+	Valid bool // Valid is true if JSONText is not NULL
+}
+
+// Scan implements the Scanner interface.
+func (n *NullJSONText) Scan(value interface{}) error {
+	if value == nil {
+		n.JSONText, n.Valid = emptyJSON, false
+		return nil
+	}
+	n.Valid = true
+	return n.JSONText.Scan(value)
+}
+
+// Value implements the driver Valuer interface.
+func (n NullJSONText) Value() (driver.Value, error) {
+	if !n.Valid {
+		return nil, nil
+	}
+	return n.JSONText.Value()
+}
+
+// BitBool is an implementation of a bool for the MySQL type BIT(1).
+// This type allows you to avoid wasting an entire byte for MySQL's boolean type TINYINT.
+type BitBool bool
+
+// Value implements the driver.Valuer interface,
+// and turns the BitBool into a bitfield (BIT(1)) for MySQL storage.
+func (b BitBool) Value() (driver.Value, error) {
+	if b {
+		return []byte{1}, nil
+	}
+	return []byte{0}, nil
+}
+
+// Scan implements the sql.Scanner interface,
+// and turns the bitfield incoming from MySQL into a BitBool
+func (b *BitBool) Scan(src interface{}) error {
+	v, ok := src.([]byte)
+	if !ok {
+		return errors.New("bad []byte type assertion")
+	}
+	*b = v[0] == 1
+	return nil
+}

+ 68 - 0
util/datetime.go

@@ -0,0 +1,68 @@
+package util
+
+import "time"
+
+const (
+	// TimeFmtLong yyyy-MM-dd hh:mm:ss
+	TimeFmtLong = `2006-01-02 15:04:05`
+	// TimeFmtNumeric yyyyMMddhhmmss
+	TimeFmtNumeric = `20060102150405`
+
+	// DateFmtLong yyyy-MM-dd
+	DateFmtLong = `2006-01-02`
+	// DateFmtNumeric yyyyMMdd
+	DateFmtNumeric = `20060102`
+)
+
+// IsTime 是否时间格式字符串
+func IsTime(s string) bool {
+	if _, err := time.ParseInLocation(TimeFmtLong, s, time.Local); err != nil {
+		return false
+	}
+	return true
+}
+
+// IsDate 是否为有效日期
+func IsDate(s string) bool {
+	if _, e := time.ParseInLocation(DateFmtLong, s, time.Local); e != nil {
+		return false
+	}
+	return true
+}
+
+// StrToTime 字符串转时间
+func StrToTime(s string) (t time.Time, err error) {
+	t, err = time.ParseInLocation(TimeFmtLong, s, time.Local)
+	return
+}
+
+// StrFmtTime 时间转字符串
+func StrFmtTime(s, fmt string) (t time.Time, err error) {
+	t, err = time.ParseInLocation(fmt, s, time.Local)
+	return
+}
+
+// TimeToStr 时间转字符串
+func TimeToStr(t time.Time) string {
+	return t.Format(TimeFmtLong)
+}
+
+// TimeFmtStr 时间转字符串
+func TimeFmtStr(t time.Time, fmt string) string {
+	return t.Format(fmt)
+}
+
+// StrToDate 字符串转日期
+func StrToDate(s string) (t time.Time, err error) {
+	t, err = time.ParseInLocation(DateFmtLong, s, time.Local)
+	return
+}
+
+// IsWeekEnd 日期是否周末
+func IsWeekEnd(d time.Weekday) bool {
+	day := int(d)
+	if day == 6 || day == 0 {
+		return true
+	}
+	return false
+}

+ 16 - 0
util/number.go

@@ -0,0 +1,16 @@
+package util
+
+import (
+	"unicode"
+)
+
+// IsDigit 字符串是否数字
+func IsDigit(s string) bool {
+	rs := []rune(s)
+	for _, r := range rs {
+		if !unicode.IsDigit(r) {
+			return false
+		}
+	}
+	return true
+}

+ 47 - 0
util/string.go

@@ -0,0 +1,47 @@
+package util
+
+import (
+	"math/rand"
+	"time"
+	"unicode/utf8"
+)
+
+// StrLen utf8 string length
+func StrLen(s string) int {
+	return utf8.RuneCountInString(s)
+}
+
+// StrMax verify string's max lenth
+func StrMax(s string, max int) bool {
+	n := utf8.RuneCountInString(s)
+	return n <= max
+}
+
+// StrMin verify string's min lenth
+func StrMin(s string, min int) bool {
+	n := utf8.RuneCountInString(s)
+	return n >= min
+}
+
+// StrRange verify string's Range lenth
+func StrRange(s string, min, max int) bool {
+	n := utf8.RuneCountInString(s)
+	if n <= max && n >= min {
+		return true
+	}
+	return false
+}
+
+// Random 指定长度的随机字符串(字母或数字)
+func Random(n int) string {
+	str := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+	bytes := []byte(str)
+	result := []byte{}
+
+	r := rand.New(rand.NewSource(time.Now().UnixNano()))
+	n1 := len(bytes)
+	for i := 0; i < n; i++ {
+		result = append(result, bytes[r.Intn(n1)])
+	}
+	return string(result)
+}