LuKaicheng 6 years ago
parent
commit
842fd3537c

+ 82 - 0
aescrypto.go

@@ -0,0 +1,82 @@
+package toolkit
+
+import (
+	"bytes"
+	"crypto/aes"
+	"crypto/cipher"
+)
+
+var (
+	key string
+)
+
+// AesCrypto define
+type AesCrypto struct {
+	Key []byte
+}
+
+// SetAesCryptoKey set key,
+// key length:16, 24, 32 bytes to AES-128, AES-192, AES-256
+func SetAesCryptoKey(password string) {
+	key = password
+}
+
+// GetAesCryptoKey get current key
+func GetAesCryptoKey() string {
+	return key
+}
+
+// NewAesCrypto new AesCrypto
+func NewAesCrypto() *AesCrypto {
+	return &AesCrypto{[]byte(key)}
+}
+
+// SetKey set key
+func (a *AesCrypto) SetKey(key string) {
+	a.Key = []byte(key)
+}
+
+// Encrypt encrypt data
+func (a *AesCrypto) Encrypt(origData []byte) ([]byte, error) {
+	block, err := aes.NewCipher(a.Key)
+	if err != nil {
+		return nil, err
+	}
+	blockSize := block.BlockSize()
+	origData = pkcs5Padding(origData, blockSize)
+
+	blockMode := cipher.NewCBCEncrypter(block, a.Key[:blockSize])
+	crypted := make([]byte, len(origData))
+
+	blockMode.CryptBlocks(crypted, origData)
+	return crypted, nil
+}
+
+// Decrypt decrypt data
+func (a *AesCrypto) Decrypt(crypted []byte) ([]byte, error) {
+	block, err := aes.NewCipher(a.Key)
+	if err != nil {
+		return nil, err
+	}
+	blockSize := block.BlockSize()
+	blockMode := cipher.NewCBCDecrypter(block, a.Key[:blockSize])
+	origData := make([]byte, len(crypted))
+
+	blockMode.CryptBlocks(origData, crypted)
+	origData = pkcs5UnPadding(origData)
+
+	return origData, nil
+}
+
+func pkcs5Padding(ciphertext []byte, blockSize int) []byte {
+	padding := blockSize - len(ciphertext)%blockSize
+	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
+	return append(ciphertext, padtext...)
+}
+
+func pkcs5UnPadding(data []byte) []byte {
+	length := len(data)
+
+	unpadding := int(data[length-1])
+	return data[:(length - unpadding)]
+}

+ 121 - 0
auth.go

@@ -0,0 +1,121 @@
+package toolkit
+
+import (
+	"errors"
+
+	jwt "github.com/dgrijalva/jwt-go"
+)
+
+// AccessToken access token
+type AccessToken struct {
+	ID      string `json:"id"`
+	Name    string `json:"name"`
+	Expires int64  `json:"expires_in"`
+}
+
+// CacheAccessToken cache access token
+type CacheAccessToken struct {
+	ID      int    `json:"id"`
+	Name    string `json:"name"`
+	Status  int    `json:"status"`
+	Expires int64  `json:"expires_in"`
+	Message string `json:"message"`
+}
+
+var (
+	accessTokenKey []byte
+)
+
+// cacheKey cache token key
+func cacheKey(id string) string {
+	return `user:user:` + id
+}
+
+// AccessTokenStorageCache storage CacheAccessToken to redis
+func AccessTokenStorageCache(id string, token CacheAccessToken) error {
+	/*
+		bytes, err := json.Marshal(token)
+		if err != nil {
+			return err
+		}
+
+			redis := NewRedisCache()
+			return redis.Set(cacheKey(id), string(bytes), 0)
+			// */
+	return errors.New(`Not found cache class`)
+}
+
+// AccessTokenGetCache get CacheAccessToken from redis
+func AccessTokenGetCache(id string) (CacheAccessToken, error) {
+	/*
+		redis := NewRedisCache()
+		data, err := redis.Get(cacheKey(id))
+		var token CacheAccessToken
+		if err != nil {
+			return token, err
+		}
+		err = json.Unmarshal([]byte(data), &token)
+		return token, err
+		// */
+	var token CacheAccessToken
+	return token, errors.New(`Not found cache class`)
+}
+
+// SetAccessTokenKey set jwt key
+func SetAccessTokenKey(key string) {
+	accessTokenKey = []byte(key)
+}
+
+// NewAccessToken new token
+func NewAccessToken(tok AccessToken) (string, error) {
+	claims := jwt.MapClaims{
+		"id":         tok.ID,
+		"name":       tok.Name,
+		"expires_in": tok.Expires}
+	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+
+	// Sign and get the complete encoded token as a string using the secret
+	return token.SignedString(accessTokenKey)
+}
+
+// ParseAccessToken parse token
+func ParseAccessToken(accessToken string) (AccessToken, error) {
+	var (
+		tok AccessToken
+		e   error
+	)
+
+	token, err := jwt.Parse(
+		accessToken,
+		func(token *jwt.Token) (interface{}, error) {
+			return accessTokenKey, nil
+		})
+
+	if token.Valid {
+		claims := token.Claims.(jwt.MapClaims)
+		if id, ok := claims["id"]; ok {
+			tok.ID = id.(string)
+		}
+		if name, ok := claims["name"]; ok {
+			tok.Name = name.(string)
+		}
+		if expires, ok := claims["expires_in"]; ok {
+			tok.Expires = int64(expires.(float64))
+		}
+		e = nil
+	} else if ve, ok := err.(*jwt.ValidationError); ok {
+		if ve.Errors&jwt.ValidationErrorMalformed != 0 {
+			//fmt.Println("That's not even a token")
+			e = errors.New(`False authentication information`)
+		} else if ve.Errors&
+			(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
+			// Token is either expired or not active yet
+			//fmt.Println("Timing is everything")
+			e = errors.New(`Authentication information expired`)
+		} else {
+			e = errors.New(`Invalid authentication information`)
+			//fmt.Println("Couldn't handle this token:", err)
+		}
+	}
+	return tok, e
+}

+ 21 - 0
binding/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Manuel Martínez-Almeida
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 108 - 0
binding/binding.go

@@ -0,0 +1,108 @@
+// 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"
+
+	validator "gopkg.in/go-playground/validator.v8"
+)
+
+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"
+)
+
+// Binding bind http request params to struct
+type Binding interface {
+	Name() string
+	Bind(*http.Request, interface{}) error
+}
+
+// StructValidator Validator
+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
+
+	// RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key
+	// NOTE: if the key already exists, the previous validation function will be replaced.
+	// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
+	RegisterValidation(string, validator.Func) error
+}
+
+var Validator StructValidator = &defaultValidator{}
+
+var (
+	JSON          = jsonBinding{}
+	XML           = xmlBinding{}
+	Form          = formBinding{}
+	Query         = queryBinding{}
+	FormPost      = formPostBinding{}
+	FormMultipart = formMultipartBinding{}
+	ProtoBuf      = protobufBinding{}
+	MsgPack       = msgpackBinding{}
+)
+
+// BindDefault default binding
+func BindDefault(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 MIMEMSGPACK, MIMEMSGPACK2:
+		return MsgPack
+	default: //case MIMEPOSTForm, MIMEMultipartPOSTForm:
+		return Form
+	}
+}
+
+func validate(obj interface{}) error {
+	if Validator == nil {
+		return nil
+	}
+
+	return Validator.ValidateStruct(obj)
+}
+
+func Bind(req *http.Request, obj interface{}) error {
+	b := BindDefault(req.Method, ContentType(req))
+	return MustBindWith(req, obj, b)
+}
+
+func MustBindWith(req *http.Request, obj interface{}, b Binding) (err error) {
+	return b.Bind(req, obj)
+}
+
+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
+}

+ 58 - 0
binding/default_validator.go

@@ -0,0 +1,58 @@
+// 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 (
+	"bytes"
+	"errors"
+	"fmt"
+	"reflect"
+	"sync"
+
+	"gopkg.in/go-playground/validator.v8"
+)
+
+type defaultValidator struct {
+	once     sync.Once
+	validate *validator.Validate
+}
+
+var _ StructValidator = &defaultValidator{}
+
+func (v *defaultValidator) ValidateStruct(obj interface{}) error {
+	if kindOfData(obj) == reflect.Struct {
+		v.lazyinit()
+		if err := v.validate.Struct(obj); err != nil {
+			buf := bytes.NewBufferString("")
+			for _, v := range err.(validator.ValidationErrors) {
+				buf.WriteString(fmt.Sprintf("%s %s %s", v.Name, v.Tag, v.Param))
+				buf.WriteString(";")
+			}
+			return errors.New(buf.String()) //error(err)
+		}
+	}
+	return nil
+}
+
+func (v *defaultValidator) RegisterValidation(key string, fn validator.Func) error {
+	v.lazyinit()
+	return v.validate.RegisterValidation(key, fn)
+}
+
+func (v *defaultValidator) lazyinit() {
+	v.once.Do(func() {
+		config := &validator.Config{TagName: "validate"}
+		v.validate = validator.New(config)
+	})
+}
+
+func kindOfData(data interface{}) reflect.Kind {
+	value := reflect.ValueOf(data)
+	valueType := value.Kind()
+	if valueType == reflect.Ptr {
+		valueType = value.Elem().Kind()
+	}
+	return valueType
+}

+ 58 - 0
binding/form.go

@@ -0,0 +1,58 @@
+// 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 * 1024 * 1024
+
+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
+	}
+	req.ParseMultipartForm(defaultMemory)
+	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 := mapForm(obj, req.MultipartForm.Value); err != nil {
+		return err
+	}
+	return validate(obj)
+}

+ 181 - 0
binding/form_mapping.go

@@ -0,0 +1,181 @@
+// 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 (
+	"errors"
+	"reflect"
+	"strconv"
+	"time"
+)
+
+func mapForm(ptr interface{}, form map[string][]string) error {
+	typ := reflect.TypeOf(ptr).Elem()
+	val := reflect.ValueOf(ptr).Elem()
+	for i := 0; i < typ.NumField(); i++ {
+		typeField := typ.Field(i)
+		structField := val.Field(i)
+		if !structField.CanSet() {
+			continue
+		}
+
+		structFieldKind := structField.Kind()
+		inputFieldName := typeField.Tag.Get("form")
+		if inputFieldName == "" {
+			inputFieldName = typeField.Name
+			// if "form" tag is nil, we inspect if the field is a struct.
+			// this would not make sense for JSON parsing but it does for a form
+			// since data is flatten
+			if structFieldKind == reflect.Struct {
+				err := mapForm(structField.Addr().Interface(), form)
+				if err != nil {
+					return err
+				}
+				continue
+			}
+		}
+		inputValue, exists := form[inputFieldName]
+		if !exists {
+			continue
+		}
+
+		numElems := len(inputValue)
+		if structFieldKind == reflect.Slice && numElems > 0 {
+			sliceOf := structField.Type().Elem().Kind()
+			slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
+			for i := 0; i < numElems; i++ {
+				if err := setWithProperType(sliceOf, inputValue[i], slice.Index(i)); err != nil {
+					return err
+				}
+			}
+			val.Field(i).Set(slice)
+		} else {
+			if _, isTime := structField.Interface().(time.Time); isTime {
+				if err := setTimeField(inputValue[0], typeField, structField); err != nil {
+					return err
+				}
+				continue
+			}
+			if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
+	switch valueKind {
+	case reflect.Int:
+		return setIntField(val, 0, structField)
+	case reflect.Int8:
+		return setIntField(val, 8, structField)
+	case reflect.Int16:
+		return setIntField(val, 16, structField)
+	case reflect.Int32:
+		return setIntField(val, 32, structField)
+	case reflect.Int64:
+		return setIntField(val, 64, structField)
+	case reflect.Uint:
+		return setUintField(val, 0, structField)
+	case reflect.Uint8:
+		return setUintField(val, 8, structField)
+	case reflect.Uint16:
+		return setUintField(val, 16, structField)
+	case reflect.Uint32:
+		return setUintField(val, 32, structField)
+	case reflect.Uint64:
+		return setUintField(val, 64, structField)
+	case reflect.Bool:
+		return setBoolField(val, structField)
+	case reflect.Float32:
+		return setFloatField(val, 32, structField)
+	case reflect.Float64:
+		return setFloatField(val, 64, structField)
+	case reflect.String:
+		structField.SetString(val)
+	default:
+		return errors.New("Unknown type")
+	}
+	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 nil
+}
+
+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 == "" {
+		return errors.New("Blank time format")
+	}
+
+	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
+	}
+
+	t, err := time.ParseInLocation(timeFormat, val, l)
+	if err != nil {
+		return err
+	}
+
+	value.Set(reflect.ValueOf(t))
+	return nil
+}
+
+// Don't pass in pointers to bind to. Can lead to bugs. See:
+// https://github.com/codegangsta/martini-contrib/issues/40
+// https://github.com/codegangsta/martini-contrib/pull/34#issuecomment-29683659
+func ensureNotPointer(obj interface{}) {
+	if reflect.TypeOf(obj).Kind() == reflect.Ptr {
+		panic("Pointers are not accepted as binding models")
+	}
+}

+ 32 - 0
binding/json.go

@@ -0,0 +1,32 @@
+// 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"
+
+	"encoding/json"
+)
+
+var (
+	EnableDecoderUseNumber = false
+)
+
+type jsonBinding struct{}
+
+func (jsonBinding) Name() string {
+	return "json"
+}
+
+func (jsonBinding) Bind(req *http.Request, obj interface{}) error {
+	decoder := json.NewDecoder(req.Body)
+	if EnableDecoderUseNumber {
+		decoder.UseNumber()
+	}
+	if err := decoder.Decode(obj); err != nil {
+		return err
+	}
+	return validate(obj)
+}

+ 24 - 0
binding/msgpack.go

@@ -0,0 +1,24 @@
+// 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"
+
+	"github.com/ugorji/go/codec"
+)
+
+type msgpackBinding struct{}
+
+func (msgpackBinding) Name() string {
+	return "msgpack"
+}
+
+func (msgpackBinding) Bind(req *http.Request, obj interface{}) error {
+	if err := codec.NewDecoder(req.Body, new(codec.MsgpackHandle)).Decode(&obj); err != nil {
+		return err
+	}
+	return validate(obj)
+}

+ 35 - 0
binding/protobuf.go

@@ -0,0 +1,35 @@
+// 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 (protobufBinding) Bind(req *http.Request, obj interface{}) error {
+
+	buf, err := ioutil.ReadAll(req.Body)
+	if err != nil {
+		return err
+	}
+
+	if err = proto.Unmarshal(buf, obj.(proto.Message)); err != nil {
+		return err
+	}
+
+	//Here it's same to return validate(obj), but util now we cann't add `binding:""` to the struct
+	//which automatically generate by gen-proto
+	//return nil
+	return validate(obj)
+}

+ 23 - 0
binding/query.go

@@ -0,0 +1,23 @@
+// 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)
+}

+ 24 - 0
binding/xml.go

@@ -0,0 +1,24 @@
+// 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/xml"
+	"net/http"
+)
+
+type xmlBinding struct{}
+
+func (xmlBinding) Name() string {
+	return "xml"
+}
+
+func (xmlBinding) Bind(req *http.Request, obj interface{}) error {
+	decoder := xml.NewDecoder(req.Body)
+	if err := decoder.Decode(obj); err != nil {
+		return err
+	}
+	return validate(obj)
+}

+ 187 - 0
cache/redis.go

@@ -0,0 +1,187 @@
+package cache
+
+import (
+	"time"
+
+	"github.com/go-redis/redis"
+)
+
+// RedisConfig config
+type RedisConfig struct {
+	Addr         string
+	Password     string
+	DialTimeout  time.Duration
+	ReadTimeout  time.Duration
+	WriteTimeout time.Duration
+	PoolTimeout  time.Duration
+	PoolSize     int
+}
+
+// RedisClusterConfig redis cluster configure
+type RedisClusterConfig struct {
+	// A seed list of host:port addresses of cluster nodes.
+	Addrs []string
+
+	// The maximum number of retries before giving up. Command is retried
+	// on network errors and MOVED/ASK redirects.
+	// Default is 16.
+	MaxRedirects int
+
+	// Enables read-only commands on slave nodes.
+	ReadOnly bool
+	// Allows routing read-only commands to the closest master or slave node.
+	RouteByLatency bool
+
+	//OnConnect func(*Conn) error
+
+	MaxRetries      int
+	MinRetryBackoff time.Duration
+	MaxRetryBackoff time.Duration
+	Password        string
+
+	DialTimeout  time.Duration
+	ReadTimeout  time.Duration
+	WriteTimeout time.Duration
+
+	// PoolSize applies per cluster node and not for the whole cluster.
+	PoolSize           int
+	PoolTimeout        time.Duration
+	IdleTimeout        time.Duration
+	IdleCheckFrequency time.Duration
+}
+
+var (
+	redisConfig        RedisConfig
+	redisClusterConfig RedisClusterConfig
+)
+
+// RedisCache define
+type RedisCache struct {
+	c  *redis.Client
+	cc *redis.ClusterClient
+}
+
+// SetRedisConfig set
+func SetRedisConfig(cfg RedisConfig) {
+	redisConfig = cfg
+}
+
+// SetRedisClusterConfig set
+func SetRedisClusterConfig(cfg RedisClusterConfig) {
+	redisClusterConfig = cfg
+}
+
+// NewRedisCache new RedisCache object
+func NewRedisCache() *RedisCache {
+	client := redis.NewClient(&redis.Options{
+		Addr:         redisConfig.Addr,
+		Password:     redisConfig.Password,
+		DialTimeout:  redisConfig.DialTimeout,
+		ReadTimeout:  redisConfig.ReadTimeout,
+		WriteTimeout: redisConfig.WriteTimeout,
+		PoolSize:     redisConfig.PoolSize,
+		PoolTimeout:  redisConfig.PoolTimeout,
+	})
+
+	client.Ping()
+	return &RedisCache{c: client}
+}
+
+// NewRedisClusterCache new RedisCluster object
+func NewRedisClusterCache() *RedisCache {
+	var config redis.ClusterOptions
+
+	config.Addrs = redisClusterConfig.Addrs
+	config.MaxRedirects = redisClusterConfig.MaxRedirects
+	config.ReadOnly = redisClusterConfig.ReadOnly
+	config.RouteByLatency = redisClusterConfig.RouteByLatency
+
+	config.MaxRetries = redisClusterConfig.MaxRetries
+	config.MinRetryBackoff = redisClusterConfig.MinRetryBackoff
+	config.MaxRetryBackoff = redisClusterConfig.MaxRetryBackoff
+	config.Password = redisClusterConfig.Password
+
+	config.DialTimeout = redisClusterConfig.DialTimeout
+	config.ReadTimeout = redisClusterConfig.ReadTimeout
+	config.WriteTimeout = redisClusterConfig.WriteTimeout
+
+	config.PoolSize = redisClusterConfig.PoolSize
+	config.PoolTimeout = redisClusterConfig.PoolTimeout
+	config.IdleTimeout = redisClusterConfig.IdleTimeout
+	config.IdleCheckFrequency = redisClusterConfig.IdleCheckFrequency
+
+	client := redis.NewClusterClient(&config)
+
+	client.Ping()
+	return &RedisCache{cc: client}
+}
+
+// Get get value from cache
+func (c RedisCache) Get(key string) (string, error) {
+	return c.c.Get(key).Result()
+}
+
+// GetCluster get value from cluster cache
+func (c RedisCache) GetCluster(key string) (string, error) {
+	return c.cc.Get(key).Result()
+}
+
+// Set set key-value to cache
+func (c RedisCache) Set(key, value string, expiration time.Duration) error {
+	return c.c.Set(key, value, expiration).Err()
+}
+
+// SetCluster set key-value to cache
+func (c RedisCache) SetCluster(key, value string, expiration time.Duration) error {
+	return c.cc.Set(key, value, expiration).Err()
+}
+
+// Subscribe subscribe message
+func (c RedisCache) Subscribe(channels string, cb func(channel string, message string, err error)) {
+	pubsub := c.c.Subscribe(channels)
+	defer pubsub.Close()
+
+	var (
+		msg *redis.Message
+		err error
+	)
+	msg, err = pubsub.ReceiveMessage()
+	for err == nil {
+		cb(msg.Channel, msg.Payload, nil)
+		msg, err = pubsub.ReceiveMessage()
+	}
+
+	cb("", "", err)
+
+	return
+}
+
+// SubscribeCluster subscribe cluster message
+func (c RedisCache) SubscribeCluster(channels string, cb func(channel string, message string, err error)) {
+	pubsub := c.cc.Subscribe(channels)
+	defer pubsub.Close()
+
+	var (
+		msg *redis.Message
+		err error
+	)
+	msg, err = pubsub.ReceiveMessage()
+	for err == nil {
+		cb(msg.Channel, msg.Payload, nil)
+		msg, err = pubsub.ReceiveMessage()
+	}
+
+	cb("", "", err)
+
+	return
+}
+
+// Publish publish message
+func (c RedisCache) Publish(channel, message string) error {
+	return c.c.Publish(channel, message).Err()
+}
+
+// PublishCluster publish message
+func (c RedisCache) PublishCluster(channel, message string) error {
+	return c.cc.Publish(channel, message).Err()
+}

+ 78 - 0
context.go

@@ -0,0 +1,78 @@
+package toolkit
+
+// ContextStringKey string key
+type ContextStringKey string
+
+const (
+	// ContextKeyGateWayPrefix api gateway route path prefix
+	ContextKeyGateWayPrefix ContextStringKey = `gateway_route_prefix`
+)
+
+// ContextKey int key
+type ContextKey int
+
+const (
+	// ContextKeyRequestMethod is populated in the context by
+	// PopulateRequestContext. Its value is r.Method.
+	ContextKeyRequestMethod ContextKey = iota
+
+	// ContextKeyRequestURI is populated in the context by
+	// PopulateRequestContext. Its value is r.RequestURI.
+	ContextKeyRequestURI
+
+	// ContextKeyRequestPath is populated in the context by
+	// PopulateRequestContext. Its value is r.URL.Path.
+	ContextKeyRequestPath
+
+	// ContextKeyRequestProto is populated in the context by
+	// PopulateRequestContext. Its value is r.Proto.
+	ContextKeyRequestProto
+
+	// ContextKeyRequestHost is populated in the context by
+	// PopulateRequestContext. Its value is r.Host.
+	ContextKeyRequestHost
+
+	// ContextKeyRequestRemoteAddr is populated in the context by
+	// PopulateRequestContext. Its value is r.RemoteAddr.
+	ContextKeyRequestRemoteAddr
+
+	// ContextKeyRequestXForwardedFor is populated in the context by
+	// PopulateRequestContext. Its value is r.Header.Get("X-Forwarded-For").
+	ContextKeyRequestXForwardedFor
+
+	// ContextKeyRequestXForwardedProto is populated in the context by
+	// PopulateRequestContext. Its value is r.Header.Get("X-Forwarded-Proto").
+	ContextKeyRequestXForwardedProto
+
+	// ContextKeyRequestAuthorization is populated in the context by
+	// PopulateRequestContext. Its value is r.Header.Get("Authorization").
+	ContextKeyRequestAuthorization
+
+	// ContextKeyRequestReferer is populated in the context by
+	// PopulateRequestContext. Its value is r.Header.Get("Referer").
+	ContextKeyRequestReferer
+
+	// ContextKeyRequestUserAgent is populated in the context by
+	// PopulateRequestContext. Its value is r.Header.Get("User-Agent").
+	ContextKeyRequestUserAgent
+
+	// ContextKeyRequestXRequestID is populated in the context by
+	// PopulateRequestContext. Its value is r.Header.Get("X-Request-Id").
+	ContextKeyRequestXRequestID
+
+	// ContextKeyRequestAccept is populated in the context by
+	// PopulateRequestContext. Its value is r.Header.Get("Accept").
+	ContextKeyRequestAccept
+
+	// ContextKeyResponseHeaders is populated in the context whenever a
+	// ServerFinalizerFunc is specified. Its value is of type http.Header, and
+	// is captured only once the entire response has been written.
+	ContextKeyResponseHeaders
+
+	// ContextKeyResponseSize is populated in the context whenever a
+	// ServerFinalizerFunc is specified. Its value is of type int64.
+	ContextKeyResponseSize
+
+	// ContextKeyAccessToken auth access token
+	ContextKeyAccessToken
+)

+ 187 - 0
db.go

@@ -0,0 +1,187 @@
+package toolkit
+
+import (
+	"database/sql"
+	"fmt"
+	"sync"
+	"time"
+	//
+	_ "github.com/go-sql-driver/mysql"
+	"github.com/jmoiron/sqlx"
+)
+
+// DbConfig config
+type DbConfig struct {
+	Driver       string
+	DNS          string
+	MaxOpenConns int
+	MaxIdle      int
+	MaxLifetime  time.Duration
+}
+
+var (
+	config DbConfig
+	db     *sqlx.DB
+	once   sync.Once
+)
+
+// DB define
+type DB struct {
+	conn *sqlx.DB
+	tx   *sqlx.Tx
+}
+
+// SetDbConfig set
+func SetDbConfig(cfg DbConfig) {
+	config.Driver = cfg.Driver
+	config.DNS = cfg.DNS
+	config.MaxOpenConns = cfg.MaxOpenConns
+	config.MaxIdle = cfg.MaxIdle
+	config.MaxLifetime = cfg.MaxLifetime * time.Second
+}
+
+// NewDB new DB object
+func NewDB() *DB {
+	return &DB{}
+}
+
+// FreeDB free db connect
+func FreeDB() {
+	if db != nil {
+		db.Close()
+	}
+}
+
+// ErrNoRows check norows error
+func ErrNoRows(err error) bool {
+	if err == sql.ErrNoRows {
+		return true
+	}
+	return false
+}
+
+func connect() (*sqlx.DB, error) {
+	if db != nil {
+		return db, nil
+	}
+
+	var err error
+	once.Do(func() {
+		db, err = sqlx.Connect(config.Driver, config.DNS)
+		if err == nil {
+			db.DB.SetMaxOpenConns(config.MaxOpenConns)
+			db.DB.SetMaxIdleConns(config.MaxIdle)
+			db.DB.SetConnMaxLifetime(config.MaxLifetime)
+			db.Ping()
+		}
+	})
+
+	return db, err
+}
+
+// Connect connect to database
+func (d *DB) Connect() (err error) {
+	d.conn, err = connect()
+	return
+}
+
+// Close close database connect
+func (d *DB) Close() {
+	//d.conn.Close()
+}
+
+// BeginTrans begin trans
+func (d *DB) BeginTrans() {
+	d.tx = d.conn.MustBegin()
+}
+
+// Commit commit
+func (d *DB) Commit() error {
+	return d.tx.Commit()
+}
+
+// Rollback rollback
+func (d *DB) Rollback() error {
+	return d.tx.Rollback()
+}
+
+// TransExec trans execute
+func (d *DB) TransExec(query string, args interface{}) (LastInsertId, RowsAffected int64, err error) {
+	if rs, err := d.tx.NamedExec(query, args); err == nil {
+		RowsAffected, _ = rs.RowsAffected()
+		LastInsertId, _ = rs.LastInsertId()
+	}
+	return
+}
+
+// Rows get rows
+func (d *DB) Rows(dest interface{}, query string, args interface{}) error {
+	err := d.Connect()
+	if err != nil {
+		return err
+	}
+	defer d.Close()
+
+	nstmt, err := d.conn.PrepareNamed(query)
+	if err != nil {
+		return err
+	}
+	defer nstmt.Close()
+
+	err = nstmt.Select(dest, args)
+
+	return err
+}
+
+// Row get row
+func (d *DB) Row(dest interface{}, query string, args interface{}) error {
+	err := d.Connect()
+	if err != nil {
+		return err
+	}
+	defer d.Close()
+
+	nstmt, err := d.conn.PrepareNamed(query)
+	if err != nil {
+		return err
+	}
+	defer nstmt.Close()
+
+	err = nstmt.Get(dest, args)
+
+	return err
+}
+
+// Insert insert into
+func (d *DB) Insert(query string, args interface{}) (LastInsertId, RowsAffected int64, err error) {
+	err = d.Connect()
+	if err != nil {
+		return
+	}
+	defer d.Close()
+
+	if rs, err := d.conn.NamedExec(query, args); err == nil {
+		LastInsertId, _ = rs.LastInsertId()
+		RowsAffected, _ = rs.RowsAffected()
+	}
+	return
+}
+
+// Update update/delete
+func (d *DB) Update(query string, args interface{}) (RowsAffected int64, err error) {
+	err = d.Connect()
+	if err != nil {
+		return
+	}
+	defer d.Close()
+
+	if rs, err := d.conn.NamedExec(query, args); err == nil {
+		RowsAffected, _ = rs.RowsAffected()
+	}
+	return
+}
+
+// Limit MySQL limit
+func (d *DB) Limit(page, pagesize int) string {
+	return fmt.Sprintf(" limit %d, %d", (page-1)*pagesize, pagesize)
+}

+ 12 - 0
example/cuser/define/router.go

@@ -0,0 +1,12 @@
+package define
+
+const (
+	// RouteUserPostSignIn user signin
+	RouteUserPostSignIn = `/sign/in`
+	// RouteUserGetProfile user profile
+	RouteUserGetProfile = `/profile`
+	// RouteUserGetList user list
+	RouteUserGetList = `/list`
+	// RouteUserPostChangePwd user changepwd
+	RouteUserPostChangePwd = `/chgpwd/:name`
+)

+ 57 - 0
example/cuser/define/user.go

@@ -0,0 +1,57 @@
+package define
+
+import (
+	"git.chuangxin1.com/csacred/toolkit/types"
+)
+
+// FormID http request form of id
+type FormID struct {
+	ID int `json:"id" form:"id" db:"id"`
+}
+
+// FormUser http request from of user
+type FormUser struct {
+	Page     int    `json:"page" db:"page" form:"page" validate:"required,gt=0"`
+	PageSize int    `json:"pagesize" db:"pagesize" form:"pagesize" validate:"required,gte=5,lte=100"`
+	Code     int    `json:"code" db:"code" form:"code"`
+	Name     string `json:"name" db:"name" form:"name" validate:"max=20"`
+	ID       int    `json:"id" db:"id" form:"id"`
+}
+
+// FormUserChangePwd http request from of user change password
+type FormUserChangePwd struct {
+	OldPwd        string `json:"passwd" form:"passwd" validate:"required,min=2,max=20"`
+	NewPwd        string `json:"newpasswd" form:"newpasswd" validate:"required,min=2,max=20"`
+	NewPwdConfirm string `json:"passwdconfirm" form:"passwdconfirm" validate:"required,min=2,max=20"`
+}
+
+// FormUserSignIn http request from of user sign
+type FormUserSignIn struct {
+	Name     string `form:"name" db:"phone" validate:"required,min=2,max=20"`
+	Password string `form:"passwd" validate:"required,min=2,max=20"`
+}
+
+// User user
+type User struct {
+	ID       int            `json:"id"`
+	Phone    string         `json:"phone"`
+	Name     string         `json:"name"`
+	Passwd   string         `json:"passwd"`
+	Sex      string         `json:"sex"`
+	Birthday types.DateText `json:"birthday"`
+	//*
+	Country  types.NullInt `json:"country"`
+	Province types.NullInt `json:"province"`
+	City     types.NullInt `json:"city"`
+	Nation   types.NullInt `json:"nation"`
+	// */
+	Avatar    types.NullString `json:"avatar"`
+	Signature types.NullString `json:"signature"`
+	Category  int              `json:"category"`
+	Scale     int              `json:"scale"`
+	Score     int              `json:"score"`
+	Balance   float32          `json:"balance"`
+	Status    int              `json:"status"`
+	Stime     string           `json:"stime"`
+	Attr      types.JSONText   `json:"attr"`
+}

+ 65 - 0
example/cuser/main.go

@@ -0,0 +1,65 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"net/http"
+	"os"
+	"time"
+
+	"git.chuangxin1.com/csacred/toolkit"
+	"git.chuangxin1.com/csacred/toolkit/example/cuser/service"
+	khttp "git.chuangxin1.com/csacred/toolkit/http"
+	"github.com/go-kit/kit/log"
+)
+
+func main() {
+	var addr = flag.String("listen", ":9122", "Address for HTTP server")
+	logger := log.NewLogfmtLogger(os.Stderr)
+
+	flag.Parse()
+	readTimeout := 30 * time.Second
+	writeTimeout := 30 * time.Second
+	maxHeaderBytes := 1 << 20
+
+	router := khttp.NewRouter()
+
+	khttp.AddGetHandle(router, "/ver", func(w http.ResponseWriter, r *http.Request) {
+		ver := map[string]string{
+			"version":  "0.2.0",
+			"comments": "chuangxin1.com API",
+			"author":   "ls"}
+		khttp.WriteJSON(w, toolkit.RowReplyData(ver))
+	})
+
+	dns := fmt.Sprintf(
+		"root:Lsar-5211@tcp(127.0.0.1:3306)/community_user?charset=utf8mb4")
+
+	dbConfig := toolkit.DbConfig{
+		Driver:       `mysql`,
+		DNS:          dns,
+		MaxOpenConns: 50,
+		MaxIdle:      5,
+		MaxLifetime:  0}
+
+	toolkit.SetDbConfig(dbConfig)
+	defer toolkit.FreeDB()
+
+	srv := service.New()
+	handers := service.GenServiceHandler(srv, logger)
+	for i := range handers {
+		fmt.Println(handers[i].Method, handers[i].Router)
+		router.Handler(handers[i].Method, handers[i].Router, handers[i].Hander)
+	}
+	//serviceRouter(router, logger)
+	//hs := make(HostSwitch)
+	//hs["192.168.1.8:9122"] = router
+	//toolkit.StartServer(addr, hs, readTimeout, writeTimeout, maxHeaderBytes, logger)
+	khttp.ListenAndServe(
+		*addr,
+		router,
+		readTimeout,
+		writeTimeout,
+		maxHeaderBytes,
+		logger)
+}

+ 57 - 0
example/cuser/service/decode.go

@@ -0,0 +1,57 @@
+package service
+
+import (
+	"context"
+	"net/http"
+
+	"git.chuangxin1.com/csacred/toolkit/binding"
+	"git.chuangxin1.com/csacred/toolkit/example/cuser/define"
+)
+
+// DecodeGetUserProfileRequest 用户基本信息请求处理
+func DecodeGetUserProfileRequest(
+	_ context.Context,
+	r *http.Request) (interface{}, error) {
+	var request define.FormID
+
+	if err := binding.Bind(r, &request); err != nil {
+		return nil, err
+	}
+	return request, nil
+}
+
+// DecodeGetUserListRequest 用户列表信息请求处理
+func DecodeGetUserListRequest(
+	_ context.Context,
+	r *http.Request) (interface{}, error) {
+	var request define.FormUser
+
+	if err := binding.Bind(r, &request); err != nil {
+		return nil, err
+	}
+	return request, nil
+}
+
+// DecodePostUserSignInRequest 用户登录
+func DecodePostUserSignInRequest(
+	ctx context.Context,
+	r *http.Request) (interface{}, error) {
+	var request define.FormUserSignIn
+
+	if err := binding.Bind(r, &request); err != nil {
+		return nil, err
+	}
+	return request, nil
+}
+
+// DecodePostUserChangePwdRequest 用户修改密码
+func DecodePostUserChangePwdRequest(
+	ctx context.Context,
+	r *http.Request) (interface{}, error) {
+	var request define.FormUserChangePwd
+
+	if err := binding.Bind(r, &request); err != nil {
+		return nil, err
+	}
+	return request, nil
+}

+ 70 - 0
example/cuser/service/endpoint.go

@@ -0,0 +1,70 @@
+package service
+
+import (
+	"context"
+
+	"github.com/go-kit/kit/endpoint"
+
+	"git.chuangxin1.com/csacred/toolkit/example/cuser/define"
+)
+
+// Endpoints define
+type Endpoints struct {
+	UserListEndpoint      endpoint.Endpoint
+	UserProfileEndpoint   endpoint.Endpoint
+	UserSignInEndpoint    endpoint.Endpoint
+	UserChangePwdEndpoint endpoint.Endpoint
+}
+
+// GenServerEndpoints service to endpoints
+func GenServerEndpoints(s UserServicer) Endpoints {
+	return Endpoints{
+		UserListEndpoint:      genUserListEndpoint(s),
+		UserProfileEndpoint:   genUserProfileEndpoint(s),
+		UserSignInEndpoint:    genUserSignInEndpoint(s),
+		UserChangePwdEndpoint: genUserChangePwdEndpoint(s),
+	}
+}
+
+func genUserListEndpoint(s UserServicer) endpoint.Endpoint {
+	return func(
+		ctx context.Context,
+		request interface{}) (response interface{}, err error) {
+		req := request.(define.FormUser)
+		response, err = s.List(ctx, req)
+		return
+	}
+}
+
+func genUserProfileEndpoint(s UserServicer) endpoint.Endpoint {
+	return func(
+		ctx context.Context,
+		request interface{}) (response interface{}, err error) {
+		req := request.(define.FormID)
+		response, err = s.Profile(ctx, req)
+		//response = toolkit.NewReplyData(toolkit.ErrOk)
+		//err = nil
+		return
+	}
+}
+
+func genUserSignInEndpoint(s UserServicer) endpoint.Endpoint {
+	return func(
+		ctx context.Context,
+		request interface{}) (response interface{}, err error) {
+		req := request.(define.FormUserSignIn)
+		response, err = s.SignIn(ctx, req)
+		return
+	}
+}
+
+func genUserChangePwdEndpoint(s UserServicer) endpoint.Endpoint {
+	return func(
+		ctx context.Context,
+		request interface{}) (response interface{}, err error) {
+		req := request.(define.FormUserChangePwd)
+
+		response, err = s.ChangePwd(ctx, req)
+		return
+	}
+}

+ 80 - 0
example/cuser/service/router.go

@@ -0,0 +1,80 @@
+package service
+
+import (
+	"context"
+	"csacred/define/router"
+
+	"git.chuangxin1.com/csacred/toolkit/example/cuser/define"
+	"git.chuangxin1.com/csacred/toolkit/http"
+	"github.com/go-kit/kit/log"
+	consulsd "github.com/go-kit/kit/sd/consul"
+)
+
+// API gateway factory
+func factory(
+	ctx context.Context,
+	instancer *consulsd.Instancer,
+	logger log.Logger) Endpoints {
+	e := Endpoints{}
+
+	var lb http.LoadBalancer
+
+	lb.Logger = logger
+	lb.Ctx = ctx
+	lb.Inst = instancer
+	lb.Dec = http.DecodeJSONResponse
+
+	lb.Route = define.RouteUserGetProfile
+	e.UserProfileEndpoint = http.GetLoadBalancer(lb)
+
+	lb.Route = define.RouteUserGetList
+	e.UserListEndpoint = http.GetLoadBalancer(lb)
+
+	lb.Route = define.RouteUserPostSignIn
+	e.UserSignInEndpoint = http.PostLoadBalancer(lb)
+
+	lb.Route = define.RouteUserPostChangePwd
+	e.UserChangePwdEndpoint = http.PostLoadBalancer(lb)
+
+	return e
+}
+
+func endpointHanders(endpoints Endpoints) []http.EndpointRouter {
+	var handers []http.EndpointRouter
+
+	handers = append(handers, http.GetEndpointRouter(router.RouteUserGetProfile, DecodeGetUserProfileRequest, http.WriteCtxJSON, endpoints.UserProfileEndpoint))
+	handers = append(handers, http.GetEndpointRouter(router.RouteUserGetList, DecodeGetUserListRequest, http.WriteCtxJSON, endpoints.UserListEndpoint))
+	handers = append(handers, http.PostEndpointRouter(router.RouteUserPostSignIn, DecodePostUserSignInRequest, http.WriteCtxJSON, endpoints.UserSignInEndpoint))
+	handers = append(handers, http.PostEndpointRouter(router.RouteUserPostChangePwd, DecodePostUserChangePwdRequest, http.WriteCtxJSON, endpoints.UserChangePwdEndpoint))
+
+	return handers
+}
+
+// GenServiceHandler service handler
+func GenServiceHandler(
+	s UserServicer,
+	logger log.Logger) []http.RouterHandler {
+	var handers []http.RouterHandler
+
+	e := GenServerEndpoints(s)
+	ehanders := endpointHanders(e)
+	for i := range ehanders {
+		handers = append(handers, http.EndpointRouterHandler(ehanders[i], logger))
+	}
+	return handers
+}
+
+// GenGatewayHandlers gateway handlers
+func GenGatewayHandlers(
+	ctx context.Context,
+	instancer *consulsd.Instancer,
+	logger log.Logger) []http.RouterHandler {
+	var handers []http.RouterHandler
+
+	e := factory(ctx, instancer, logger)
+	ehanders := endpointHanders(e)
+	for i := range ehanders {
+		handers = append(handers, http.EndpointRouterHandler(ehanders[i], logger))
+	}
+	return handers
+}

+ 140 - 0
example/cuser/service/user.go

@@ -0,0 +1,140 @@
+package service
+
+import (
+	"context"
+	"fmt"
+
+	"git.chuangxin1.com/csacred/toolkit"
+	"git.chuangxin1.com/csacred/toolkit/http"
+
+	"app/model"
+
+	"git.chuangxin1.com/csacred/toolkit/example/cuser/define"
+)
+
+const (
+	// ServiceName microservice name
+	ServiceName = `user`
+)
+
+// UserServicer define
+type UserServicer interface {
+	List(context.Context, define.FormUser) (*toolkit.ReplyData, error)
+	Profile(context.Context, define.FormID) (*toolkit.ReplyData, error)
+	SignIn(context.Context, define.FormUserSignIn) (*toolkit.ReplyData, error)
+	ChangePwd(
+		context.Context,
+		define.FormUserChangePwd) (*toolkit.ReplyData, error)
+}
+
+type userService struct{}
+
+// New UserServicer
+func New() UserServicer {
+	var s UserServicer
+	s = userService{}
+
+	return s
+}
+
+func (userService) List(
+	ctx context.Context,
+	u define.FormUser) (reply *toolkit.ReplyData, err error) {
+	var user model.UserModel
+	fmt.Println("user.list")
+	if rows, total, err := user.List(u); err == nil {
+		reply = toolkit.RowsReplyData(total, rows)
+	} else {
+		reply = toolkit.ErrReplyData(toolkit.ErrDataNotFound, `没有用户数据`)
+	}
+	return
+}
+
+func (userService) Profile(
+	ctx context.Context,
+	u define.FormID) (reply *toolkit.ReplyData, err error) {
+	//*
+	var user model.UserModel
+
+	if row, err := user.Get(u); err == nil {
+		reply = toolkit.RowReplyData(row)
+	} else {
+		reply = toolkit.ErrReplyData(toolkit.ErrDataNotFound, `没有用户信息`)
+	}
+
+	/*
+		ver := map[string]string{
+			"version":  "0.2.0",
+			"comments": "chuangxin1.com API",
+			"author":   "ls"}
+		reply = toolkit.RowReplyData(ver)
+		err = nil
+		// */
+
+	return
+}
+
+func (userService) SignIn(
+	ctx context.Context,
+	u define.FormUserSignIn) (reply *toolkit.ReplyData, err error) {
+	err = nil
+	/*
+		reply = toolkit.NewReplyData(toolkit.ErrOk)
+		var (
+			uid    int
+			token  toolkit.AccessToken
+			ctoken toolkit.CacheAccessToken
+		)
+		aes := toolkit.NewAesCrypto()
+
+		userID := "100036"
+		id, _ := aes.Encrypt([]byte(userID))
+		//base64.StdEncoding()
+
+		t := time.Now()
+		t = t.Add(2 * time.Hour)
+
+		expires := t.Unix()
+		token.ID = base64.StdEncoding.EncodeToString([]byte(id))
+		token.Name = "ls0 ❄️"
+		token.Expires = expires
+
+		accessToken, _ := toolkit.NewAccessToken(token)
+		data := map[string]interface{}{
+			toolkit.VarUserAuthorization: accessToken,
+			"expires_in":                 expires,
+		}
+		reply.Data = data
+
+		uid, err = strconv.Atoi(userID)
+		ctoken.ID = uid
+		ctoken.Name = token.Name
+		ctoken.Expires = expires
+		ctoken.Status = 0
+		ctoken.Message = `ok`
+
+		err = toolkit.AccessTokenStorageCache(userID, ctoken)
+		fmt.Println("access_token cache", err)
+	 // */
+	return
+}
+
+func (userService) ChangePwd(
+	ctx context.Context,
+	u define.FormUserChangePwd) (reply *toolkit.ReplyData, err error) {
+	err = nil
+	reply = toolkit.NewReplyData(toolkit.ErrOk)
+
+	//fmt.Println(u)
+	reply.Data = http.RouteVars(ctx)
+	/*
+		if vars := toolkit.RouteVars(ctx); vars != nil {
+			fmt.Println("route vars name", vars["name"])
+		}
+		fmt.Println(toolkit.JWTToken, ctx.Value(toolkit.JWTToken))
+		// */
+	return
+}
+
+// UserServiceMiddleware is a chainable behavior modifier for UserServicer.
+type UserServiceMiddleware func(UserServicer) UserServicer

+ 80 - 0
example/cuser/vendor/app/model/user.go

@@ -0,0 +1,80 @@
+package model
+
+import (
+	"bytes"
+	"fmt"
+
+	"git.chuangxin1.com/csacred/toolkit"
+
+	"git.chuangxin1.com/csacred/toolkit/example/cuser/define"
+)
+
+// UserModel user model layer
+type UserModel struct {
+}
+
+// Get get user info
+func (m *UserModel) Get(u define.FormID) (user define.User, err error) {
+	var db = toolkit.NewDB()
+	//if err = db.Connect(); err == nil {
+	//defer db.Close()
+	var (
+		buffer bytes.Buffer
+		where  bytes.Buffer
+	)
+
+	buffer.WriteString(`select id, phone, name, password passwd, sex, `)
+	buffer.WriteString(`birthday, country, province, city, nation, avatar, `)
+	buffer.WriteString(`signature, category, balance, scale, score, attr, `)
+	buffer.WriteString(`status, sys_time stime from usr_user`)
+	where.WriteString(` where id > 0 and id = :id`)
+
+	buffer.WriteString(where.String())
+
+	err = db.Row(&user, buffer.String(), u)
+	//}
+
+	return
+}
+
+// List get users by page
+func (m *UserModel) List(
+	formUser define.FormUser) (users []define.User, total int, err error) {
+	var db = toolkit.NewDB()
+	//if err = db.Connect(); err == nil {
+	//defer db.Close()
+	var (
+		buffer bytes.Buffer
+		where  bytes.Buffer
+		sql    bytes.Buffer
+	)
+
+	buffer.WriteString(`select id, phone, name, password passwd, sex, `)
+	buffer.WriteString(`birthday, country, province, city, nation, avatar, `)
+	buffer.WriteString(`signature, category, balance, scale, score, attr, `)
+	buffer.WriteString(`status, sys_time stime `)
+
+	where.WriteString(` from usr_user where id > 0`)
+
+	if len(formUser.Name) > 0 {
+		where.WriteString(` and name like :name`)
+	}
+
+	sql.WriteString(`select count(*) total`)
+	sql.WriteString(where.String())
+	buffer.WriteString(where.String())
+	buffer.WriteString(` order by id desc`)
+	buffer.WriteString(db.Limit(formUser.Page, formUser.PageSize))
+	fmt.Println(buffer.String(), sql.String())
+	err = db.Row(&total, sql.String(), formUser)
+	fmt.Println("total", total)
+	if err != nil {
+		return
+	}
+
+	err = db.Rows(&users, buffer.String(), formUser)
+	fmt.Println("err:", err)
+	//}
+
+	return
+}

+ 6 - 0
example/cuser/vendor/vendor.json

@@ -0,0 +1,6 @@
+{
+	"comment": "",
+	"ignore": "test",
+	"package": [],
+	"rootPath": "git.chuangxin1.com/csacred/toolkit/example/cuser"
+}

+ 57 - 0
example/gateway/main.go

@@ -0,0 +1,57 @@
+package main
+
+import (
+	"context"
+	"flag"
+	"os"
+	"time"
+
+	"git.chuangxin1.com/csacred/toolkit"
+	"git.chuangxin1.com/csacred/toolkit/http"
+	"github.com/go-kit/kit/log"
+)
+
+func main() {
+	var (
+		addr       = flag.String("listen", ":9999", "Address for HTTP server")
+		consulAddr = flag.String("consul", "", "Consul agent address")
+	)
+	flag.Parse()
+
+	// Logging domain.
+	var logger log.Logger
+	{
+		logger = log.NewLogfmtLogger(os.Stderr)
+		logger = log.With(logger, "ts", log.DefaultTimestampUTC)
+		logger = log.With(logger, "caller", log.DefaultCaller)
+	}
+
+	router := http.NewRouter()
+	ctx := context.Background()
+
+	options := toolkit.ServiceOptions{}
+	if len(*consulAddr) > 0 {
+		options.Address = *consulAddr
+	}
+
+	genUserHandlers(ctx, router, options, logger)
+	//genWechatHandlers(ctx, router, options, logger)
+
+	var (
+		readTimeout    time.Duration
+		writeTimeout   time.Duration
+		maxHeaderBytes int
+	)
+
+	readTimeout = 5 * time.Second
+	writeTimeout = 10 * time.Second
+	maxHeaderBytes = 1024 * 1024
+
+	http.ListenAndServe(
+		*addr,
+		router,
+		readTimeout,
+		writeTimeout,
+		maxHeaderBytes,
+		logger)
+}

+ 55 - 0
example/gateway/user.go

@@ -0,0 +1,55 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"os"
+
+	"git.chuangxin1.com/csacred/toolkit"
+	"github.com/chuangxin1/httprouter"
+	"github.com/go-kit/kit/log"
+
+	users "git.chuangxin1.com/csacred/toolkit/example/cuser/service"
+)
+
+func genUserHandlers(
+	ctx context.Context,
+	router *httprouter.Router,
+	options toolkit.ServiceOptions,
+	logger log.Logger) {
+	ver := "v1"
+	tags := []string{ver}
+	passingOnly := true
+	name := users.ServiceName
+
+	instancer, err := toolkit.NewConsulInstancer(
+		options,
+		name,
+		tags,
+		passingOnly,
+		logger)
+	if err != nil {
+		fmt.Println("NewConsulInstancer", err.Error())
+		os.Exit(0)
+	}
+
+	handers := users.GenGatewayHandlers(ctx, instancer, logger)
+	prefix := "/" + name + "/" + ver
+	//ctx = context.WithValue(ctx, contextKeyPrefix, prefix)
+	for i := range handers {
+		fmt.Println(handers[i].Method, prefix+handers[i].Router)
+		hander := handers[i].Hander
+		router.Handle(
+			handers[i].Method,
+			prefix+handers[i].Router,
+			func(w http.ResponseWriter, req *http.Request) {
+				req = req.WithContext(context.WithValue(
+					req.Context(),
+					toolkit.ContextKeyGateWayPrefix,
+					prefix))
+				hander.ServeHTTP(w, req)
+			})
+		//handers[i].Hander)
+	}
+}

+ 182 - 0
example/server/httpserver.go

@@ -0,0 +1,182 @@
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"net/http"
+	"os"
+	"time"
+
+	"git.chuangxin1.com/csacred/toolkit"
+	"git.chuangxin1.com/csacred/toolkit/binding"
+	khttp "git.chuangxin1.com/csacred/toolkit/http"
+	"github.com/chuangxin1/toolkit/types"
+	"github.com/go-kit/kit/log"
+)
+
+// FormID http request from of user
+type FormID struct {
+	ID int `json:"id" db:"id" form:"id"`
+}
+
+// FormUser http request from of user
+type FormUser struct {
+	Page     int    `json:"page" db:"page" form:"page" validate:"required,gt=0"`
+	PageSize int    `json:"pagesize" db:"pagesize" form:"pagesize" validate:"required,gte=5,lte=100"`
+	Code     int    `json:"code" db:"code" form:"code"`
+	Name     string `json:"name" db:"name" form:"name" validate:"max=20"`
+	ID       int    `json:"id" db:"id" form:"id"`
+}
+
+// User user
+type User struct {
+	ID       int            `json:"id"`
+	Phone    string         `json:"phone"`
+	Name     string         `json:"name"`
+	Passwd   string         `json:"passwd"`
+	Sex      string         `json:"sex"`
+	Birthday types.DateText `json:"birthday"`
+	//*
+	Country  types.NullInt `json:"country"`
+	Province types.NullInt `json:"province"`
+	City     types.NullInt `json:"city"`
+	Nation   types.NullInt `json:"nation"`
+	// */
+	Avatar    types.NullString `json:"avatar"`
+	Signature types.NullString `json:"signature"`
+	Category  int              `json:"category"`
+	Scale     int              `json:"scale"`
+	Score     int              `json:"score"`
+	Balance   float32          `json:"balance"`
+	Status    int              `json:"status"`
+	Stime     string           `json:"stime"`
+	Attr      types.JSONText   `json:"attr"`
+}
+
+func get(u FormID) (user User, err error) {
+	var db = toolkit.NewDB()
+	//if err = db.Connect(); err == nil {
+	//defer db.Close()
+	var (
+		buffer bytes.Buffer
+		where  bytes.Buffer
+	)
+
+	buffer.WriteString(`select id, phone, name, password passwd, sex, `)
+	buffer.WriteString(`birthday, country, province, city, nation, avatar, `)
+	buffer.WriteString(`signature, category, balance, scale, score, attr, `)
+	buffer.WriteString(`status, sys_time stime from usr_user`)
+	where.WriteString(` where id > 0 and id = :id`)
+
+	buffer.WriteString(where.String())
+
+	err = db.Row(&user, buffer.String(), u)
+	//}
+
+	return
+}
+
+func list(formUser FormUser) (users []User, total int, err error) {
+	var db = toolkit.NewDB()
+	//if err = db.Connect(); err == nil {
+	//defer db.Close()
+	var (
+		buffer bytes.Buffer
+		where  bytes.Buffer
+		sql    bytes.Buffer
+	)
+
+	buffer.WriteString(`select id, phone, name, password passwd, sex, `)
+	buffer.WriteString(`birthday, country, province, city, nation, avatar, `)
+	buffer.WriteString(`signature, category, balance, scale, score, attr, `)
+	buffer.WriteString(`status, sys_time stime `)
+
+	where.WriteString(` from usr_user where id > 0`)
+
+	if len(formUser.Name) > 0 {
+		where.WriteString(` and name like :name`)
+	}
+
+	sql.WriteString(`select count(*) total`)
+	sql.WriteString(where.String())
+	buffer.WriteString(where.String())
+	buffer.WriteString(` order by id desc`)
+	buffer.WriteString(db.Limit(formUser.Page, formUser.PageSize))
+	//fmt.Println(buffer.String(), sql.String())
+	err = db.Row(&total, sql.String(), formUser)
+	//fmt.Println("total", total)
+	if err != nil {
+		return
+	}
+
+	err = db.Rows(&users, buffer.String(), formUser)
+	return
+}
+
+func main() {
+	logger := log.NewLogfmtLogger(os.Stderr)
+
+	readTimeout := 30 * time.Second
+	writeTimeout := 30 * time.Second
+	maxHeaderBytes := 1 << 20
+
+	router := khttp.NewRouter()
+
+	khttp.AddGetHandle(router, "/ver", func(w http.ResponseWriter, r *http.Request) {
+		ver := map[string]string{
+			"version":  "0.2.0",
+			"comments": "chuangxin1.com API",
+			"author":   "ls"}
+		khttp.WriteJSON(w, toolkit.RowReplyData(ver))
+	})
+
+	khttp.AddGetHandle(router, "/users", func(w http.ResponseWriter, r *http.Request) {
+		var user FormUser
+
+		user.Page = 1
+		user.PageSize = 20
+		users, total, err := list(user)
+		if err != nil {
+			khttp.WriteJSON(w, toolkit.ErrReplyData(toolkit.ErrDataNotFound, err.Error()))
+			return
+		}
+		khttp.WriteJSON(w, toolkit.RowsReplyData(total, users))
+	})
+
+	khttp.AddGetHandle(router, "/profile", func(w http.ResponseWriter, r *http.Request) {
+		var user FormID
+
+		binding.Bind(r, &user)
+		users, err := get(user)
+		if err != nil {
+			khttp.WriteJSON(w, toolkit.ErrReplyData(toolkit.ErrDataNotFound, err.Error()))
+			return
+		}
+		khttp.WriteJSON(w, toolkit.RowReplyData(users))
+	})
+
+	dns := fmt.Sprintf(
+		"root:Lsar-5211@tcp(127.0.0.1:3306)/community_user?charset=utf8mb4")
+
+	dbConfig := toolkit.DbConfig{
+		Driver:       `mysql`,
+		DNS:          dns,
+		MaxOpenConns: 50,
+		MaxIdle:      5,
+		MaxLifetime:  0}
+
+	toolkit.SetDbConfig(dbConfig)
+	defer toolkit.FreeDB()
+
+	//serviceRouter(router, logger)
+	//hs := make(HostSwitch)
+	//hs["192.168.1.8:9122"] = router
+	//toolkit.StartServer(addr, hs, readTimeout, writeTimeout, maxHeaderBytes, logger)
+	khttp.ListenAndServe(
+		":9006",
+		router,
+		readTimeout,
+		writeTimeout,
+		maxHeaderBytes,
+		logger)
+}

+ 29 - 0
hash.go

@@ -0,0 +1,29 @@
+package toolkit
+
+import (
+	"crypto/md5"
+	"crypto/sha1"
+	"crypto/sha256"
+	"encoding/hex"
+)
+
+// SHA2 hash string
+func SHA2(data string) string {
+	hash := sha256.New()
+	hash.Write([]byte(data))
+	return hex.EncodeToString(hash.Sum(nil))
+}
+
+// MD5 hash string
+func MD5(data string) string {
+	hash := md5.New()
+	hash.Write([]byte(data))
+	return hex.EncodeToString(hash.Sum(nil))
+}
+
+// SHA1 hash string
+func SHA1(data string) string {
+	hash := sha1.New()
+	hash.Write([]byte(data))
+	return hex.EncodeToString(hash.Sum(nil))
+}

+ 106 - 0
http/endpoint.go

@@ -0,0 +1,106 @@
+package http
+
+import (
+	khttp "net/http"
+
+	"github.com/go-kit/kit/endpoint"
+	"github.com/go-kit/kit/log"
+)
+
+// RouterHandler hander
+type RouterHandler struct {
+	Method string
+	Router string
+	Hander khttp.Handler
+}
+
+// EndpointRouter endpoint hander
+type EndpointRouter struct {
+	Method   string
+	Router   string
+	Dec      DecodeRequestFunc
+	Enc      EncodeResponseFunc
+	Endpoint endpoint.Endpoint
+}
+
+// EndpointRouterHandler router to handler
+func EndpointRouterHandler(router EndpointRouter, logger log.Logger) RouterHandler {
+	return RouterHandler{
+		Method: router.Method,
+		Router: router.Router,
+		Hander: NewTansportServer(
+			router.Endpoint,
+			router.Dec,
+			router.Enc,
+			logger)}
+}
+
+// GetEndpointRouter add GET endpoint
+func GetEndpointRouter(router string, dec DecodeRequestFunc, enc EncodeResponseFunc, endpoint endpoint.Endpoint) EndpointRouter {
+	return EndpointRouter{
+		Method:   `GET`,
+		Router:   router,
+		Dec:      dec,
+		Enc:      enc,
+		Endpoint: endpoint}
+}
+
+// PostEndpointRouter add POST endpoint
+func PostEndpointRouter(router string, dec DecodeRequestFunc, enc EncodeResponseFunc, endpoint endpoint.Endpoint) EndpointRouter {
+	return EndpointRouter{
+		Method:   `POST`,
+		Router:   router,
+		Dec:      dec,
+		Enc:      enc,
+		Endpoint: endpoint}
+}
+
+// PutEndpointRouter add PUT endpoint
+func PutEndpointRouter(router string, dec DecodeRequestFunc, enc EncodeResponseFunc, endpoint endpoint.Endpoint) EndpointRouter {
+	return EndpointRouter{
+		Method:   `PUT`,
+		Router:   router,
+		Dec:      dec,
+		Enc:      enc,
+		Endpoint: endpoint}
+}
+
+// DeleteEndpointRouter add DELETE endpoint
+func DeleteEndpointRouter(router string, dec DecodeRequestFunc, enc EncodeResponseFunc, endpoint endpoint.Endpoint) EndpointRouter {
+	return EndpointRouter{
+		Method:   `DELETE`,
+		Router:   router,
+		Dec:      dec,
+		Enc:      enc,
+		Endpoint: endpoint}
+}
+
+// HeadEndpointRouter add HEAD endpoint
+func HeadEndpointRouter(router string, dec DecodeRequestFunc, enc EncodeResponseFunc, endpoint endpoint.Endpoint) EndpointRouter {
+	return EndpointRouter{
+		Method:   `HEAD`,
+		Router:   router,
+		Dec:      dec,
+		Enc:      enc,
+		Endpoint: endpoint}
+}
+
+// OptionsEndpointRouter add OPTIONS endpoint
+func OptionsEndpointRouter(router string, dec DecodeRequestFunc, enc EncodeResponseFunc, endpoint endpoint.Endpoint) EndpointRouter {
+	return EndpointRouter{
+		Method:   `OPTIONS`,
+		Router:   router,
+		Dec:      dec,
+		Enc:      enc,
+		Endpoint: endpoint}
+}
+
+// PatchEndpointRouter add PATCH endpoint
+func PatchEndpointRouter(router string, dec DecodeRequestFunc, enc EncodeResponseFunc, endpoint endpoint.Endpoint) EndpointRouter {
+	return EndpointRouter{
+		Method:   `PATCH`,
+		Router:   router,
+		Dec:      dec,
+		Enc:      enc,
+		Endpoint: endpoint}
+}

+ 115 - 0
http/loadbalancer.go

@@ -0,0 +1,115 @@
+package http
+
+import (
+	"context"
+	"io"
+	"net/url"
+	"strings"
+	"time"
+
+	"github.com/go-kit/kit/endpoint"
+	"github.com/go-kit/kit/log"
+	"github.com/go-kit/kit/sd"
+	"github.com/go-kit/kit/sd/lb"
+
+	consulsd "github.com/go-kit/kit/sd/consul"
+)
+
+// dec DecodeRequestFunc, enc EncodeResponseFunc
+
+// LoadBalancer endpoint
+type LoadBalancer struct {
+	Ctx    context.Context
+	Inst   *consulsd.Instancer
+	Route  string
+	Dec    DecodeResponseFunc
+	Logger log.Logger
+}
+
+var (
+	// retry max
+	retryMax = 3
+	// retry timeout
+	retryTimeout = 500 * time.Millisecond
+)
+
+func factory(ctx context.Context, method, router string, dec DecodeResponseFunc) sd.Factory {
+	return func(instance string) (endpoint.Endpoint, io.Closer, error) {
+		if !strings.HasPrefix(instance, "http") {
+			instance = "http://" + instance
+		}
+		tgt, err := url.Parse(instance)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		return ClientRequestEndpoint(ctx, tgt, method, router, dec), nil, nil
+	}
+}
+
+// FactoryLoadBalancer factory load balance
+func FactoryLoadBalancer(ctx context.Context, instancer *consulsd.Instancer, method, route string, dec DecodeResponseFunc, logger log.Logger) endpoint.Endpoint {
+	endpointer := sd.NewEndpointer(
+		instancer,
+		factory(ctx, method, route, dec),
+		logger)
+	balancer := lb.NewRoundRobin(endpointer)
+
+	return lb.Retry(retryMax, retryTimeout, balancer)
+}
+
+// GetLoadBalancer add GET router to load balance
+func GetLoadBalancer(loadbalancer LoadBalancer) endpoint.Endpoint {
+	endpointer := sd.NewEndpointer(loadbalancer.Inst, factory(loadbalancer.Ctx, `GET`, loadbalancer.Route, loadbalancer.Dec), loadbalancer.Logger)
+	balancer := lb.NewRoundRobin(endpointer)
+
+	return lb.Retry(retryMax, retryTimeout, balancer)
+}
+
+// PostLoadBalancer add POST router to load balance
+func PostLoadBalancer(loadbalancer LoadBalancer) endpoint.Endpoint {
+	endpointer := sd.NewEndpointer(loadbalancer.Inst, factory(loadbalancer.Ctx, `POST`, loadbalancer.Route, loadbalancer.Dec), loadbalancer.Logger)
+	balancer := lb.NewRoundRobin(endpointer)
+
+	return lb.Retry(retryMax, retryTimeout, balancer)
+}
+
+// PutLoadBalancer add PUT router to load balance
+func PutLoadBalancer(loadbalancer LoadBalancer) endpoint.Endpoint {
+	endpointer := sd.NewEndpointer(loadbalancer.Inst, factory(loadbalancer.Ctx, `PUT`, loadbalancer.Route, loadbalancer.Dec), loadbalancer.Logger)
+	balancer := lb.NewRoundRobin(endpointer)
+
+	return lb.Retry(retryMax, retryTimeout, balancer)
+}
+
+// HeadLoadBalancer add HEAD router to load balance
+func HeadLoadBalancer(loadbalancer LoadBalancer) endpoint.Endpoint {
+	endpointer := sd.NewEndpointer(loadbalancer.Inst, factory(loadbalancer.Ctx, `HEAD`, loadbalancer.Route, loadbalancer.Dec), loadbalancer.Logger)
+	balancer := lb.NewRoundRobin(endpointer)
+
+	return lb.Retry(retryMax, retryTimeout, balancer)
+}
+
+// DeleteLoadBalancer add DELETE router to load balance
+func DeleteLoadBalancer(loadbalancer LoadBalancer) endpoint.Endpoint {
+	endpointer := sd.NewEndpointer(loadbalancer.Inst, factory(loadbalancer.Ctx, `DELETE`, loadbalancer.Route, loadbalancer.Dec), loadbalancer.Logger)
+	balancer := lb.NewRoundRobin(endpointer)
+
+	return lb.Retry(retryMax, retryTimeout, balancer)
+}
+
+// OptionsLoadBalancer add OPTIONS router to load balance
+func OptionsLoadBalancer(loadbalancer LoadBalancer) endpoint.Endpoint {
+	endpointer := sd.NewEndpointer(loadbalancer.Inst, factory(loadbalancer.Ctx, `OPTIONS`, loadbalancer.Route, loadbalancer.Dec), loadbalancer.Logger)
+	balancer := lb.NewRoundRobin(endpointer)
+
+	return lb.Retry(retryMax, retryTimeout, balancer)
+}
+
+// PatchLoadBalancer add PATCH router to load balance
+func PatchLoadBalancer(loadbalancer LoadBalancer) endpoint.Endpoint {
+	endpointer := sd.NewEndpointer(loadbalancer.Inst, factory(loadbalancer.Ctx, `PATCH`, loadbalancer.Route, loadbalancer.Dec), loadbalancer.Logger)
+	balancer := lb.NewRoundRobin(endpointer)
+
+	return lb.Retry(retryMax, retryTimeout, balancer)
+}

+ 179 - 0
http/request.go

@@ -0,0 +1,179 @@
+package http
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"reflect"
+	"strconv"
+	"strings"
+
+	"github.com/chuangxin1/httprouter"
+	"github.com/go-kit/kit/circuitbreaker"
+	"github.com/go-kit/kit/endpoint"
+	"github.com/go-kit/kit/sd"
+	"github.com/go-kit/kit/sd/lb"
+	"github.com/sony/gobreaker"
+
+	"git.chuangxin1.com/csacred/toolkit"
+	httptransport "github.com/go-kit/kit/transport/http"
+)
+
+// EncodeResponseFunc encodes the passed response object to the HTTP response
+// writer. It's designed to be used in HTTP servers, for server-side
+// endpoints. One straightforward EncodeResponseFunc could be something that
+// JSON encodes the object directly to the response body.
+type EncodeResponseFunc func(context.Context, http.ResponseWriter, interface{}) error
+
+// DecodeResponseFunc extracts a user-domain response object from an HTTP
+// response object. It's designed to be used in HTTP clients, for client-side
+// endpoints. One straightforward DecodeResponseFunc could be something that
+// JSON decodes from the response body to the concrete response type.
+type DecodeResponseFunc func(context.Context, *http.Response) (response interface{}, err error)
+
+// RouteVars returns the route variables for the current request, if any.
+func RouteVars(ctx context.Context) map[string]string {
+	return httprouter.ContextVars(ctx)
+}
+
+// ContextVars returns the
+func ContextVars(ctx context.Context, key interface{}) interface{} {
+	return ctx.Value(key)
+}
+
+// CopyURL copy url
+func CopyURL(base *url.URL, path string) *url.URL {
+	next := *base
+	next.Path = path
+	return &next
+}
+
+func routePath(ctx context.Context, req *http.Request) {
+	path := req.URL.Path
+	prefix, _ := ctx.Value(toolkit.ContextKeyGateWayPrefix).(string)
+	routePath := httprouter.ContextRoutePath(ctx)
+	if prefix != "" && routePath != "" {
+		if strings.Contains(routePath, prefix) {
+			absolutePath := routePath[len(prefix):]
+			if absolutePath != path {
+				req.URL.Path = absolutePath
+			}
+		}
+	}
+}
+
+// URLValuesStruct convert struct to url.Values
+func URLValuesStruct(obj interface{}) url.Values {
+	t := reflect.TypeOf(obj)
+	v := reflect.ValueOf(obj)
+	values := url.Values{}
+	for i := 0; i < t.NumField(); i++ {
+		key := t.Field(i).Tag.Get("form")
+		value := format(v.Field(i), v.Field(i).Interface())
+
+		values.Add(key, value)
+	}
+	return values
+}
+
+func format(v reflect.Value, data interface{}) string {
+	var s string
+	switch v.Kind() {
+	case reflect.String:
+		s = data.(string)
+	case reflect.Int:
+		s = strconv.FormatInt(int64(data.(int)), 10)
+	case reflect.Uint:
+		s = strconv.FormatUint(data.(uint64), 10)
+	case reflect.Bool:
+		s = strconv.FormatBool(data.(bool))
+	case reflect.Float32:
+	case reflect.Float64:
+		s = strconv.FormatFloat(data.(float64), 'f', -1, 32)
+	default:
+		s = "" // fmt.Sprintf("unsupported kind %s", v.Type())
+	}
+	return s
+}
+
+// ClientEncodeGetRequest client get encode request
+func ClientEncodeGetRequest(ctx context.Context, req *http.Request, request interface{}) error {
+	values := URLValuesStruct(request)
+
+	auth, ok := ctx.Value(toolkit.ContextKeyRequestAuthorization).(string)
+	if ok {
+		req.Header.Set(toolkit.HTTPHeaderAuthorization, auth)
+	}
+	routePath(ctx, req)
+	token, _ := ctx.Value(toolkit.ContextKeyAccessToken).(string)
+	if token != "" {
+		values.Set(toolkit.VarUserAuthorization, token)
+	}
+
+	req.URL.RawQuery = values.Encode()
+	return nil
+}
+
+// ClientEncodeJSONRequest is an EncodeRequestFunc that serializes the request
+// as a JSON object to the Request body. Many JSON-over-HTTP services can use
+// it as a sensible default. If the request implements Headerer, the provided
+// headers will be applied to the request.
+func ClientEncodeJSONRequest(ctx context.Context, req *http.Request, request interface{}) error {
+	req.Header.Set("Content-Type", "application/json; charset=utf-8")
+	routePath(ctx, req)
+	if headerer, ok := request.(httptransport.Headerer); ok {
+		for k := range headerer.Headers() {
+			req.Header.Set(k, headerer.Headers().Get(k))
+		}
+	}
+	if auth, ok := ctx.Value(toolkit.ContextKeyRequestAuthorization).(string); ok {
+		req.Header.Set("Authorization", auth)
+	}
+	values := url.Values{}
+	token, _ := ctx.Value(toolkit.ContextKeyAccessToken).(string)
+	if token != "" {
+		values.Set(toolkit.VarUserAuthorization, token)
+	}
+	req.URL.RawQuery = values.Encode()
+
+	var b bytes.Buffer
+	req.Body = ioutil.NopCloser(&b)
+	return json.NewEncoder(&b).Encode(request)
+}
+
+// ClientRequestEndpoint client request Endpoint
+func ClientRequestEndpoint(ctx context.Context, u *url.URL, method, router string, dec DecodeResponseFunc) endpoint.Endpoint {
+	var e endpoint.Endpoint
+	options := []httptransport.ClientOption{}
+	var enc httptransport.EncodeRequestFunc
+
+	switch method {
+	case "POST":
+		enc = ClientEncodeJSONRequest
+	default:
+		enc = ClientEncodeGetRequest
+	}
+	e = httptransport.NewClient(
+		method,
+		CopyURL(u, router),
+		enc,
+		httptransport.DecodeResponseFunc(dec),
+		options...,
+	).Endpoint()
+
+	e = circuitbreaker.Gobreaker(
+		gobreaker.NewCircuitBreaker(gobreaker.Settings{}))(e)
+	//e = ratelimit.NewErroringLimiter(rate.NewLimiter(rate.Every(time.Second), qps))(e)
+
+	return e
+}
+
+// ClientLoadBalancer load balance
+func ClientLoadBalancer(endpoints sd.FixedEndpointer) endpoint.Endpoint {
+	balancer := lb.NewRoundRobin(endpoints)
+
+	return lb.Retry(retryMax, retryTimeout, balancer)
+}

+ 133 - 0
http/response.go

@@ -0,0 +1,133 @@
+package http
+
+import (
+	"context"
+	"encoding/json"
+	"encoding/xml"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"strings"
+
+	"git.chuangxin1.com/csacred/toolkit"
+)
+
+// DecodeRequestFunc extracts a user-domain request object from an HTTP
+// request object. It's designed to be used in HTTP servers, for server-side
+// endpoints. One straightforward DecodeRequestFunc could be something that
+// JSON decodes from the request body to the concrete response type.
+type DecodeRequestFunc func(context.Context, *http.Request) (request interface{}, err error)
+
+func header(w http.ResponseWriter, contentType string) {
+	w.Header().Set(`Content-Type`, contentType)
+	w.Header().Set(`X-Power`, toolkit.LibName+`/`+toolkit.LibVersion)
+	w.WriteHeader(http.StatusOK)
+}
+
+// WriteJSON response JSON data.
+func WriteJSON(w http.ResponseWriter, response interface{}) error {
+	header(w, `application/json; charset=utf-8`)
+	return json.NewEncoder(w).Encode(response)
+}
+
+// WriteXML response XML data.
+func WriteXML(w http.ResponseWriter, response interface{}) error {
+	header(w, `application/xml; charset=utf-8`)
+	return xml.NewEncoder(w).Encode(response)
+}
+
+// WriteBytes response bytes
+func WriteBytes(w http.ResponseWriter, response interface{}) error {
+	header(w, `text/html; charset=utf-8`)
+	w.Write(response.([]byte))
+	return nil
+}
+
+// WriteCtxJSON response JSON data.
+func WriteCtxJSON(ctx context.Context, w http.ResponseWriter, response interface{}) error {
+	return WriteJSON(w, response)
+}
+
+// WriteCtxXML response XML data.
+func WriteCtxXML(ctx context.Context, w http.ResponseWriter, response interface{}) error {
+	return WriteXML(w, response)
+}
+
+// WriteCtxBytes response text data.
+func WriteCtxBytes(ctx context.Context, w http.ResponseWriter, response interface{}) error {
+	return WriteBytes(w, response)
+}
+
+// EncodeError request encode error response
+func EncodeError(_ context.Context, err error, w http.ResponseWriter) {
+	WriteJSON(w, toolkit.ErrReplyData(toolkit.ErrParamsError, err.Error()))
+}
+
+// EncodeXMLError request encode error response
+func EncodeXMLError(_ context.Context, err error, w http.ResponseWriter) {
+	WriteXML(w, toolkit.ErrReplyData(toolkit.ErrParamsError, err.Error()))
+}
+
+// DecodeJSONResponse decode client
+func DecodeJSONResponse(ctx context.Context, r *http.Response) (interface{}, error) {
+	var res toolkit.ReplyData
+	if err := json.NewDecoder(r.Body).Decode(&res); err != nil {
+		return toolkit.ErrReplyData(toolkit.ErrException, `data format error`), err
+	}
+	return res, nil
+}
+
+// DecodeXMLResponse decode client
+func DecodeXMLResponse(ctx context.Context, r *http.Response) (interface{}, error) {
+	var res toolkit.ReplyData
+	if err := xml.NewDecoder(r.Body).Decode(&res); err != nil {
+		return toolkit.ErrReplyData(toolkit.ErrException, `data format error`), err
+	}
+	return res, nil
+}
+
+// DecodeBytesResponse decode client
+func DecodeBytesResponse(ctx context.Context, r *http.Response) (interface{}, error) {
+	return ioutil.ReadAll(r.Body)
+}
+
+// PopulateRequestContext is a RequestFunc that populates several values into
+// the context from the HTTP request. Those values may be extracted using the
+// corresponding ContextKey type in this package.
+func PopulateRequestContext(ctx context.Context, r *http.Request) context.Context {
+	var accessToken string
+	accessToken = r.URL.Query().Get(toolkit.VarUserAuthorization)
+	if accessToken == "" {
+		if cookie, err := r.Cookie(toolkit.VarUserAuthorization); err == nil {
+			accessToken, _ = url.QueryUnescape(cookie.Value)
+		}
+	}
+
+	token := r.Header.Get(toolkit.HTTPHeaderAuthorization)
+	if accessToken == "" {
+		if len(token) > 6 && strings.ToUpper(token[0:7]) == "BEARER " {
+			accessToken = token[7:]
+		}
+	}
+
+	for k, v := range map[toolkit.ContextKey]string{
+		toolkit.ContextKeyRequestMethod:          r.Method,
+		toolkit.ContextKeyRequestURI:             r.RequestURI,
+		toolkit.ContextKeyRequestPath:            r.URL.Path,
+		toolkit.ContextKeyRequestProto:           r.Proto,
+		toolkit.ContextKeyRequestHost:            r.Host,
+		toolkit.ContextKeyRequestRemoteAddr:      r.RemoteAddr,
+		toolkit.ContextKeyRequestXForwardedFor:   r.Header.Get("X-Forwarded-For"),
+		toolkit.ContextKeyRequestXForwardedProto: r.Header.Get("X-Forwarded-Proto"),
+		toolkit.ContextKeyRequestAuthorization:   token,
+		toolkit.ContextKeyRequestReferer:         r.Header.Get("Referer"),
+		toolkit.ContextKeyRequestUserAgent:       r.Header.Get("User-Agent"),
+		toolkit.ContextKeyRequestXRequestID:      r.Header.Get("X-Request-Id"),
+		toolkit.ContextKeyRequestAccept:          r.Header.Get("Accept"),
+		toolkit.ContextKeyAccessToken:            accessToken,
+	} {
+		//fmt.Println(k, v)
+		ctx = context.WithValue(ctx, k, v)
+	}
+	return ctx
+}

+ 84 - 0
http/router.go

@@ -0,0 +1,84 @@
+package http
+
+import (
+	"net/http"
+	"runtime"
+
+	"git.chuangxin1.com/csacred/toolkit"
+	"github.com/chuangxin1/httprouter"
+)
+
+// Handle is a function that can be registered to a route to handle HTTP
+// requests. Like http.HandlerFunc.
+type Handle func(http.ResponseWriter, *http.Request)
+
+// NewRouter new http router with default route
+func NewRouter() *httprouter.Router {
+	router := httprouter.New()
+
+	router.NotFound = http.HandlerFunc(notFoundHandler)
+	router.PanicHandler = panicHandler
+
+	router.GET("/", defaultHandler)
+	router.GET("/health", defaultHandler)
+	//router.Handler("GET", "/metrics", promhttp.Handler())
+
+	return router
+}
+
+func defaultHandler(w http.ResponseWriter, r *http.Request) {
+	ver := map[string]string{
+		"version":  "0.2.0",
+		"comments": "chuangxin1.com API"}
+	WriteJSON(w, toolkit.RowReplyData(ver))
+}
+
+func panicHandler(w http.ResponseWriter, r *http.Request, err interface{}) {
+	e := err.(runtime.Error)
+
+	WriteJSON(w, toolkit.ErrReplyData(toolkit.ErrException, e.Error()))
+}
+
+func notFoundHandler(w http.ResponseWriter, req *http.Request) {
+	WriteJSON(w, toolkit.ErrReplyData(toolkit.ErrNotFound, `NotFound`))
+}
+
+// AddHandler add handler to router
+func AddHandler(router *httprouter.Router, method, route string, handler http.Handler) {
+	router.Handler(method, route, handler)
+}
+
+// AddGetHandle add handle to router
+func AddGetHandle(router *httprouter.Router, route string, handle Handle) {
+	router.GET(route, httprouter.Handle(handle))
+}
+
+// AddPostHandle add handle to router
+func AddPostHandle(router *httprouter.Router, route string, handle Handle) {
+	router.POST(route, httprouter.Handle(handle))
+}
+
+// AddPutHandle add handle to router
+func AddPutHandle(router *httprouter.Router, route string, handle Handle) {
+	router.PUT(route, httprouter.Handle(handle))
+}
+
+// AddHeadHandle add handle to router
+func AddHeadHandle(router *httprouter.Router, route string, handle Handle) {
+	router.HEAD(route, httprouter.Handle(handle))
+}
+
+// AddDeleteHandle add handle to router
+func AddDeleteHandle(router *httprouter.Router, route string, handle Handle) {
+	router.DELETE(route, httprouter.Handle(handle))
+}
+
+// AddPatchHandle add handle to router
+func AddPatchHandle(router *httprouter.Router, route string, handle Handle) {
+	router.PATCH(route, httprouter.Handle(handle))
+}
+
+// AddOptionsHandle add handle to router
+func AddOptionsHandle(router *httprouter.Router, route string, handle Handle) {
+	router.OPTIONS(route, httprouter.Handle(handle))
+}

+ 62 - 0
http/server.go

@@ -0,0 +1,62 @@
+package http
+
+import (
+	"fmt"
+	"net/http"
+	"os"
+	"os/signal"
+	"syscall"
+	"time"
+
+	"github.com/go-kit/kit/endpoint"
+	"github.com/go-kit/kit/log"
+	httptransport "github.com/go-kit/kit/transport/http"
+)
+
+// TansportServerOptions default http transport options
+func TansportServerOptions(logger log.Logger) []httptransport.ServerOption {
+	return []httptransport.ServerOption{
+		httptransport.ServerErrorLogger(logger),
+		httptransport.ServerErrorEncoder(EncodeError),
+		httptransport.ServerBefore(PopulateRequestContext),
+	}
+}
+
+// NewTansportServer new server hander
+func NewTansportServer(e endpoint.Endpoint, dec DecodeRequestFunc, enc EncodeResponseFunc, logger log.Logger) *httptransport.Server {
+	options := TansportServerOptions(logger)
+	return httptransport.NewServer(
+		e,
+		httptransport.DecodeRequestFunc(dec),
+		httptransport.EncodeResponseFunc(enc),
+		options...,
+	)
+}
+
+// ListenAndServe new server and start
+func ListenAndServe(addr string, router http.Handler, readTimeout, writeTimeout time.Duration, maxHeaderBytes int, logger log.Logger) {
+	server := &http.Server{
+		Addr:           addr,
+		Handler:        router,
+		ReadTimeout:    readTimeout,
+		WriteTimeout:   writeTimeout,
+		MaxHeaderBytes: maxHeaderBytes,
+	}
+	// Interrupt handler.
+	errc := make(chan error)
+	go func() {
+		c := make(chan os.Signal)
+		signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
+		errc <- fmt.Errorf("%s", <-c)
+	}()
+
+	// HTTP transport.
+	go func() {
+		logger.Log("Protocol", "HTTP", "addr", addr)
+		errc <- server.ListenAndServe()
+		logger.Log("Exit server", "Quit")
+	}()
+
+	// Run!
+	logger.Log("Exit", <-errc)
+}

+ 63 - 0
micro.go

@@ -0,0 +1,63 @@
+package toolkit
+
+import (
+	"time"
+
+	"github.com/go-kit/kit/log"
+	consulsd "github.com/go-kit/kit/sd/consul"
+	consulapi "github.com/hashicorp/consul/api"
+)
+
+// ServiceOptions server options
+type ServiceOptions struct {
+	// Address is the address of the Consul server
+	Address string
+
+	// Scheme is the URI scheme for the Consul server
+	Scheme string
+
+	// Datacenter to use. If not provided, the default agent datacenter is used.
+	Datacenter string
+
+	// Transport is the Transport to use for the http client.
+	//Transport *http.Transport
+
+	// HttpClient is the client to use. Default will be
+	// used if not provided.
+	//HttpClient *http.Client
+
+	// HttpAuth is the auth info to use for http access.
+	//HttpAuth *HttpBasicAuth
+
+	// WaitTime limits how long a Watch will block. If not provided,
+	// the agent default values will be used.
+	WaitTime time.Duration
+
+	// Token is used to provide a per-request ACL token
+	// which overrides the agent's default token.
+	Token string
+
+	//TLSConfig TLSConfig
+}
+
+// NewConsulInstancer consul Instancer
+func NewConsulInstancer(options ServiceOptions, name string, tags []string, passingOnly bool, logger log.Logger) (instancer *consulsd.Instancer, err error) {
+	var client consulsd.Client
+
+	config := consulapi.DefaultConfig()
+	config.Address = options.Address
+	config.Scheme = options.Scheme
+	config.Datacenter = options.Datacenter
+	config.WaitTime = options.WaitTime
+	config.Token = options.Token
+
+	consulClient, err := consulapi.NewClient(config)
+	if err != nil {
+		//logger.Log("err", err)
+		return
+	}
+	client = consulsd.NewClient(consulClient)
+	instancer = consulsd.NewInstancer(client, logger, name, tags, passingOnly)
+
+	return
+}

+ 72 - 0
middleware/auth.go

@@ -0,0 +1,72 @@
+package middleware
+
+import (
+	"context"
+	"encoding/base64"
+	"errors"
+	"time"
+	//"strings"
+
+	"github.com/go-kit/kit/endpoint"
+)
+
+type jwtKey string
+
+const (
+	// JWTToken jwt access token
+	JWTToken jwtKey = `jwt_access_token`
+)
+
+func checkAuth(accessToken AccessToken) (CacheAccessToken, error) {
+	var (
+		id, uid []byte
+		err     error
+		token   CacheAccessToken
+	)
+	id, err = base64.StdEncoding.DecodeString(accessToken.ID)
+	if err != nil {
+		return token, err
+	}
+	aes := NewAesCrypto()
+	uid, err = aes.Decrypt(id)
+	if err != nil {
+		return token, err
+	}
+
+	token, err = AccessTokenGetCache(string(uid))
+	t := time.Now()
+	if token.Expires <= t.Unix() {
+		err = errors.New(`Authentication information expired`)
+	}
+
+	return token, err
+}
+
+// AuthMiddleware auth
+func AuthMiddleware() endpoint.Middleware {
+	return func(next endpoint.Endpoint) endpoint.Endpoint {
+		return func(
+			ctx context.Context,
+			request interface{}) (interface{}, error) {
+			if token, ok := ctx.Value(ContextKeyAccessToken).(string); ok {
+				var (
+					tok    AccessToken
+					ctoken CacheAccessToken
+					err    error
+				)
+				if token == "" {
+					return NewReplyData(ErrUnAuthorized), nil
+				}
+				if tok, err = ParseAccessToken(token); err != nil {
+					return ErrReplyData(ErrUnAuthorized, err.Error()), nil
+				}
+				if ctoken, err = checkAuth(tok); err != nil {
+					return ErrReplyData(ErrUnAuthorized, err.Error()), nil
+				}
+				ctx = context.WithValue(ctx, JWTToken, ctoken)
+				return next(ctx, request)
+			}
+			return NewReplyData(ErrUnAuthorized), nil
+		}
+	}
+}

+ 30 - 0
types/bool.go

@@ -0,0 +1,30 @@
+package types
+
+import (
+	"database/sql/driver"
+	"errors"
+)
+
+// 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
+}

+ 50 - 0
types/gzip.go

@@ -0,0 +1,50 @@
+package types
+
+import (
+	"bytes"
+	"compress/gzip"
+	"database/sql/driver"
+	"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.(type) {
+	case string:
+		source = []byte(src.(string))
+	case []byte:
+		source = src.([]byte)
+	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
+}

+ 103 - 0
types/json.go

@@ -0,0 +1,103 @@
+package types
+
+import (
+	"database/sql/driver"
+	"encoding/json"
+	"errors"
+)
+
+// 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 = JSONText(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()
+}

+ 50 - 0
types/numeric.go

@@ -0,0 +1,50 @@
+package types
+
+import (
+	"database/sql/driver"
+	"errors"
+)
+
+// NullInt is an implementation of a int for the MySQL type int/tinyint ....
+type NullInt int
+
+// Value implements the driver.Valuer interface,
+// and turns the bytes into a integer for MySQL storage.
+func (n NullInt) Value() (driver.Value, error) {
+	return NullInt(n), nil
+}
+
+// Scan implements the sql.Scanner interface,
+// and turns the bytes incoming from MySQL into a integer
+func (n *NullInt) Scan(src interface{}) error {
+	if src != nil {
+		v, ok := src.(int64)
+		if !ok {
+			return errors.New("bad []byte type assertion")
+		}
+
+		*n = NullInt(v)
+		return nil
+	}
+	*n = NullInt(0)
+	return nil
+}
+
+// NullFloat is an implementation of a int for the MySQL type numeric ....
+type NullFloat float64
+
+// Scan implements the sql.Scanner interface,
+// and turns the bytes incoming from MySQL into a numeric
+func (n *NullFloat) Scan(src interface{}) error {
+	if src != nil {
+		v, ok := src.(int64)
+		if !ok {
+			return errors.New("bad []byte type assertion")
+		}
+
+		*n = NullFloat(v)
+		return nil
+	}
+	*n = NullFloat(0)
+	return nil
+}

+ 32 - 0
types/string.go

@@ -0,0 +1,32 @@
+package types
+
+import (
+	"database/sql/driver"
+	"errors"
+)
+
+// NullString is an implementation of a string for the MySQL type char/varchar ....
+type NullString string
+
+// Value implements the driver.Valuer interface,
+// and turns the string into a bytes for MySQL storage.
+func (s NullString) Value() (driver.Value, error) {
+	return []byte(s), nil
+}
+
+// Scan implements the sql.Scanner interface,
+// and turns the bytes incoming from MySQL into a string
+func (s *NullString) Scan(src interface{}) error {
+	if src != nil {
+		v, ok := src.([]byte)
+		if !ok {
+			return errors.New("bad []byte type assertion")
+		}
+
+		*s = NullString(v)
+		return nil
+	}
+
+	*s = NullString("")
+	return nil
+}

+ 39 - 0
types/time.go

@@ -0,0 +1,39 @@
+package types
+
+import (
+	"database/sql/driver"
+	"errors"
+	"time"
+)
+
+// DateText is an implementation of a string for the MySQL type date.
+type DateText string
+
+const (
+	formtDate = `2006-01-02`
+)
+
+// Value implements the driver.Valuer interface,
+// and turns the date into a DateText (date) for MySQL storage.
+func (d DateText) Value() (driver.Value, error) {
+	t, err := time.Parse(formtDate, string(d))
+	if err != nil {
+		return nil, err
+	}
+	return DateText(t.Format(formtDate)), nil
+}
+
+// Scan implements the sql.Scanner interface,
+// and turns the bitfield incoming from MySQL into a Date
+func (d *DateText) Scan(src interface{}) error {
+	v, ok := src.([]byte)
+	if !ok {
+		return errors.New("bad []byte type assertion")
+	}
+	t, err := time.Parse(formtDate, string(v))
+	if err != nil {
+		return err
+	}
+	*d = DateText(t.Format(formtDate))
+	return nil
+}

+ 8 - 0
version.go

@@ -0,0 +1,8 @@
+package toolkit
+
+var (
+	// LibName library name
+	LibName = `gtk`
+	// LibVersion library version
+	LibVersion = `0.2.0`
+)