ls 5 years ago
commit
cc6f61da77
38 changed files with 3200 additions and 0 deletions
  1. 8 0
      LICENSE
  2. 3 0
      README.md
  3. 83 0
      aes.go
  4. 21 0
      binding/LICENSE
  5. 112 0
      binding/binding.go
  6. 52 0
      binding/default_validator.go
  7. 58 0
      binding/form.go
  8. 181 0
      binding/form_mapping.go
  9. 32 0
      binding/json.go
  10. 25 0
      binding/msgpack.go
  11. 35 0
      binding/protobuf.go
  12. 23 0
      binding/query.go
  13. 24 0
      binding/xml.go
  14. 27 0
      cache/mem.go
  15. 260 0
      cache/redis.go
  16. 23 0
      context.go
  17. 357 0
      db.go
  18. 29 0
      hash.go
  19. 25 0
      html.go
  20. 246 0
      http.go
  21. 78 0
      logger.go
  22. 131 0
      reply.go
  23. 88 0
      router.go
  24. 18 0
      sys/files.go
  25. 24 0
      sys/limit.go
  26. 65 0
      tcp/server.go
  27. 30 0
      types/bool.go
  28. 39 0
      types/date.go
  29. 50 0
      types/gzip.go
  30. 103 0
      types/json.go
  31. 59 0
      types/numeric.go
  32. 32 0
      types/string.go
  33. 1 0
      url.go
  34. 8 0
      ver.go
  35. 312 0
      wechat/client.go
  36. 288 0
      wechat/pay.go
  37. 28 0
      wechat/server.go
  38. 222 0
      wechat/wechat.go

+ 8 - 0
LICENSE

@@ -0,0 +1,8 @@
+MIT License
+Copyright (c) <year> <copyright holders>
+
+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.

+ 3 - 0
README.md

@@ -0,0 +1,3 @@
+# util
+
+csacred.com chuangxin1.com util

+ 83 - 0
aes.go

@@ -0,0 +1,83 @@
+package util
+
+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])
+	if unpadding >= length {
+		return []byte(``)
+	}
+	return data[:(length - unpadding)]
+}

+ 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.

+ 112 - 0
binding/binding.go

@@ -0,0 +1,112 @@
+// 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"
+	//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 {
+	return nil
+	/*
+		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
+}

+ 52 - 0
binding/default_validator.go

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

+ 25 - 0
binding/msgpack.go

@@ -0,0 +1,25 @@
+// 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 (
+	"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)
+		// */
+	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)
+}

+ 27 - 0
cache/mem.go

@@ -0,0 +1,27 @@
+package cache
+
+/*
+var (
+	cache sync.Map
+)
+
+// Get key from cache
+func Get(key interface{}) (interface{}, bool) {
+	return cache.Load(key)
+}
+
+// Set key-value to cache
+func Set(key, value interface{}) {
+	cache.Store(key, value)
+}
+
+// Del delete key from cache
+func Del(key interface{}) {
+	cache.Delete(key)
+}
+
+// Range list cache
+func Range(cb func(k, v interface{}) bool) {
+	cache.Range(cb)
+}
+// */

+ 260 - 0
cache/redis.go

@@ -0,0 +1,260 @@
+package cache
+
+import (
+	"sync"
+	"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
+	once               sync.Once
+	cache              *RedisCache
+	isCluster          bool
+)
+
+// 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().Result()
+	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()
+}
+
+// Del Del value from cache
+func (c RedisCache) Del(key string) (int64, error) {
+	return c.c.Del(key).Result()
+}
+
+// DelCluster Del value from cluster cache
+func (c RedisCache) DelCluster(key string) (int64, error) {
+	return c.cc.Del(key).Result()
+}
+
+// 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()
+}
+
+func connect() (*RedisCache, error) {
+	if cache != nil {
+		return cache, nil
+	}
+	//*
+	var err error
+	once.Do(func() {
+		isCluster = false
+		if len(redisConfig.Addr) > 0 {
+			cache = NewRedisCache()
+		}
+
+		if len(redisClusterConfig.Addrs) > 0 {
+			cache = NewRedisClusterCache()
+			isCluster = true
+		}
+	})
+	//*/
+	return cache, err
+}
+
+// Get key from cache
+func Get(key string) (string, error) {
+	c, err := connect()
+	if err != nil {
+		return "", err
+	}
+	if isCluster {
+		return c.GetCluster(key)
+	}
+	return c.Get(key)
+}
+
+// Set key-value to cache
+func Set(key, value string, expiration time.Duration) error {
+	c, err := connect()
+	if err != nil {
+		return err
+	}
+	if isCluster {
+		return c.SetCluster(key, value, expiration)
+	}
+	return c.Set(key, value, expiration)
+}
+
+// Del delete key from cache
+func Del(key string) (int64, error) {
+	c, err := connect()
+	if err != nil {
+		return 0, err
+	}
+	if isCluster {
+		return c.DelCluster(key)
+	}
+	return c.Del(key)
+}

+ 23 - 0
context.go

@@ -0,0 +1,23 @@
+package util
+
+import (
+	"context"
+	"net/http"
+)
+
+// ContextKey context key type
+type ContextKey string
+
+// ContextGet get from context
+func ContextGet(ctx context.Context, key interface{}) interface{} {
+	return ctx.Value(key)
+}
+
+// ContextSet set to context
+func ContextSet(r *http.Request, key, val interface{}) *http.Request {
+	if val == nil {
+		return r
+	}
+
+	return r.WithContext(context.WithValue(r.Context(), key, val))
+}

+ 357 - 0
db.go

@@ -0,0 +1,357 @@
+package util
+
+import (
+	"database/sql"
+	"errors"
+	"fmt"
+	"sync"
+	"time"
+
+	// mysql
+	_ "github.com/go-sql-driver/mysql"
+	"github.com/jmoiron/sqlx"
+)
+
+const (
+	// DBErrCodeOK ok
+	DBErrCodeOK = 0
+	// DBErrCodeException exception
+	DBErrCodeException = 1
+	// DBErrCodeExists exists
+	DBErrCodeExists = 2
+	// DBErrCodeNotFound not found
+	DBErrCodeNotFound = 3
+	// DBErrCodeAuthorized authorized
+	DBErrCodeAuthorized = 4
+	// DBErrCodeNotConnect connect error
+	DBErrCodeNotConnect = 5
+)
+
+// DbConfig config
+type DbConfig struct {
+	Driver       string
+	DNS          string
+	MaxOpenConns int
+	MaxIdle      int
+	MaxLifetime  time.Duration
+}
+
+// DbReply db exec return insert/update/delete
+type DbReply struct {
+	OK           bool
+	Err          error
+	LastErr      error
+	ErrCode      int
+	LastID       int64
+	RowsAffected int64
+}
+
+// DbReplyToReply db reply to respinse
+func DbReplyToReply(reply DbReply) *ReplyData {
+	status := ErrOk
+	if !reply.OK {
+		switch reply.ErrCode {
+		case DBErrCodeException:
+			status = ErrException
+		case DBErrCodeExists:
+			status = ErrDataExists
+		case DBErrCodeNotFound:
+			status = ErrDataNotFound
+		case DBErrCodeAuthorized:
+			status = ErrUnAuthorized
+		case DBErrCodeNotConnect:
+			status = ErrNotFound
+		}
+		return ErrReplyData(status, reply.LastErr.Error())
+	}
+
+	return OkReplyData()
+}
+
+var (
+	config DbConfig
+	db     *sqlx.DB
+	err    error
+	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
+}
+
+// ErrSQLNoRows check norows error
+func ErrSQLNoRows(err error) bool {
+	if err == sql.ErrNoRows {
+		return true
+	}
+	return false
+}
+
+// DbReplyOk exec ok
+func DbReplyOk(rowsAffected, lastID int64) DbReply {
+	var reply DbReply
+
+	reply.OK = true
+	reply.ErrCode = 0
+	reply.LastID = lastID
+	reply.RowsAffected = rowsAffected
+
+	return reply
+}
+
+// DbReplyFaild exec faild
+func DbReplyFaild(errCode int, err, errText error) DbReply {
+	var reply DbReply
+
+	reply.OK = false
+	reply.ErrCode = errCode
+	reply.LastID = -1
+	reply.RowsAffected = -1
+
+	reply.Err = err
+	reply.LastErr = errText
+
+	return reply
+}
+
+// NewDB new DB object
+func NewDB() *DB {
+	return &DB{}
+}
+
+// ReleaseDB free db connect
+func ReleaseDB() {
+	if db != nil {
+		db.Close()
+	}
+}
+
+// NewConfigDB new DB dynamic object
+func NewConfigDB(config DbConfig) (dbx *DB, err error) {
+	dbx = &DB{}
+
+	dbx.conn, err = sqlx.Connect(config.Driver, config.DNS)
+	if err == nil {
+		dbx.conn.SetMaxOpenConns(config.MaxOpenConns)
+		dbx.conn.SetMaxIdleConns(config.MaxIdle)
+		dbx.conn.SetConnMaxLifetime(config.MaxLifetime)
+		dbx.conn.Ping()
+	}
+
+	return
+}
+
+// ReleaseConfigDB free db connect
+func ReleaseConfigDB(dbx *DB) {
+	if dbx.conn != nil {
+		dbx.conn.Close()
+	}
+}
+
+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) {
+	if d.conn != nil {
+		return
+	}
+
+	d.conn, err = connect()
+
+	return
+}
+
+// Close close database connect
+func (d *DB) Close() {
+	//d.conn.Close()
+}
+
+// BeginTrans begin trans
+func (d *DB) BeginTrans() (err error) {
+	d.conn, err = connect()
+	if err != nil {
+		return
+	}
+	d.tx = d.conn.MustBegin()
+	return
+}
+
+// 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
+}
+
+// TransUpdate trans update
+func (d *DB) TransUpdate(query string, args interface{}) (reply DbReply) {
+	var (
+		err error
+		rs  sql.Result
+	)
+
+	if rs, err = d.tx.NamedExec(query, args); err == nil {
+		a, _ := rs.RowsAffected()
+		reply = DbReplyOk(a, 0)
+	} else {
+		reply = DbReplyFaild(DBErrCodeException, err, errors.New(`数据执行错误`))
+	}
+	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
+}
+
+// InsertReply insert and return DbReply
+func (d *DB) InsertReply(query string, args interface{}) (reply DbReply) {
+	var (
+		err error
+		rs  sql.Result
+	)
+	err = d.Connect()
+	if err != nil {
+		reply = DbReplyFaild(DBErrCodeNotConnect, err, errors.New(`数据库连接错误`))
+		return
+	}
+	defer d.Close()
+
+	if rs, err = d.conn.NamedExec(query, args); err == nil {
+		a, _ := rs.RowsAffected()
+		n, _ := rs.LastInsertId()
+		reply = DbReplyOk(a, n)
+	} else {
+		reply = DbReplyFaild(DBErrCodeException, err, errors.New(`数据执行错误`))
+	}
+	return
+}
+
+// UpdateReply update/delete and return DbReply
+func (d *DB) UpdateReply(query string, args interface{}) (reply DbReply) {
+	var (
+		err error
+		rs  sql.Result
+	)
+	err = d.Connect()
+	if err != nil {
+		reply = DbReplyFaild(DBErrCodeNotConnect, err, errors.New(`数据库连接错误`))
+		return
+	}
+	defer d.Close()
+
+	if rs, err = d.conn.NamedExec(query, args); err == nil {
+		a, _ := rs.RowsAffected()
+		reply = DbReplyOk(a, 0)
+	} else {
+		reply = DbReplyFaild(DBErrCodeException, err, errors.New(`数据执行错误`))
+	}
+	return
+}
+
+// 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)
+}

+ 29 - 0
hash.go

@@ -0,0 +1,29 @@
+package util
+
+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))
+}

+ 25 - 0
html.go

@@ -0,0 +1,25 @@
+package util
+
+import "strings"
+
+// HTMLUnEscape html special char convert
+// 	 &quot; to space, &amp; to &, &lt; to <, &gt; to >
+func HTMLUnEscape(url string) string {
+	s := strings.Replace(url, "&quot;", "\"", -1)
+	s = strings.Replace(s, "&amp;", "&", -1)
+	s = strings.Replace(s, "&lt;", "<", -1)
+	s = strings.Replace(s, "&gt;", ">", -1)
+
+	return s
+}
+
+// HTMLEscape html special char convert
+// 	 space to &quot;, & to &amp;, < to &lt;, > to &gt;
+func HTMLEscape(url string) string {
+	s := strings.Replace(url, "\"", "&quot;", -1)
+	s = strings.Replace(s, "&", "&amp;", -1)
+	s = strings.Replace(s, "<", "&lt;", -1)
+	s = strings.Replace(s, ">", "&gt;", -1)
+
+	return s
+}

+ 246 - 0
http.go

@@ -0,0 +1,246 @@
+package util
+
+import (
+	"compress/gzip"
+	"context"
+	"encoding/json"
+	"encoding/xml"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"log"
+	"net"
+	"net/http"
+	"os"
+	"os/signal"
+	"syscall"
+	"time"
+)
+
+const (
+	// RequestTimeOut http request timeout (second)
+	RequestTimeOut = 30
+)
+
+// RouterHandler hander
+type RouterHandler struct {
+	Method string
+	Router string
+	Hander http.Handler
+}
+
+// Message HTTP response
+type Message struct {
+	StatusCode int
+	Body       []byte
+	Header     http.Header
+}
+
+func header(w http.ResponseWriter, contentType string) {
+	w.Header().Set(`Content-Type`, contentType)
+	w.Header().Set(`X-Powered-By`, LibName+`/`+LibVersion)
+	w.WriteHeader(http.StatusOK)
+}
+
+// Redirect redirect
+func Redirect(w http.ResponseWriter, url string) {
+	w.Header().Set(`Location`, url)
+	w.WriteHeader(http.StatusFound)
+}
+
+// GetRealIP get real IP from Request
+func GetRealIP(req *http.Request) (ip string) {
+	if ips := req.Header["X-Real-Ip"]; ips != nil {
+		ip = ips[0]
+	}
+	return
+}
+
+// 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
+}
+
+// WriteBytesContentType response bytes and Content-Type
+func WriteBytesContentType(w http.ResponseWriter, response interface{}, contentType string) error {
+	header(w, contentType)
+	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)
+}
+
+// ResponseJSON reply JSON
+func ResponseJSON(reply *ReplyData, err error, req *http.Request, w http.ResponseWriter) error {
+	if err != nil {
+		return WriteJSON(w, ErrReplyData(ErrException, err.Error()))
+	}
+	return WriteJSON(w, reply)
+}
+
+// HTTPListenAndServe new server and start
+func HTTPListenAndServe(addr string, router http.Handler) {
+	server := &http.Server{
+		Addr:    addr,
+		Handler: router,
+		//ReadTimeout:    30 * time.Second,
+		//WriteTimeout:   30 * time.Second,
+		MaxHeaderBytes: 1 << 20,
+	}
+	// 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() {
+		log.Println("HTTP Server listen on", addr)
+		//logger.Log("Protocol", "HTTP", "addr", addr)
+		errc <- server.ListenAndServe()
+		log.Println("Exit HTTP server", "Quit")
+	}()
+
+	// Run!
+	log.Println("Exit", <-errc)
+}
+
+func newRequest(method, uri string, header map[string]string, body io.Reader) (res *http.Response, err error) {
+	client := &http.Client{Transport: &http.Transport{
+		Dial: func(netw, addr string) (net.Conn, error) {
+			conn, err := net.DialTimeout(netw, addr, time.Second*RequestTimeOut)
+			if err != nil {
+				return nil, err
+			}
+			conn.SetDeadline(time.Now().Add(time.Second * RequestTimeOut))
+			return conn, nil
+		},
+		ResponseHeaderTimeout: time.Second * RequestTimeOut,
+	}}
+	var (
+		req *http.Request
+	)
+
+	if body != nil {
+		req, err = http.NewRequest(method, uri, body)
+	} else {
+		req, err = http.NewRequest(method, uri, nil)
+	}
+
+	if err != nil {
+		return
+	}
+
+	for k := range header {
+		req.Header.Add(k, header[k])
+	}
+
+	res, err = client.Do(req)
+
+	return
+}
+
+// Post HTTP request POST
+func Post(uri string, header map[string]string, data io.Reader) (msg Message, err error) {
+	var res *http.Response
+	if res, err = newRequest("POST", uri, header, data); err != nil {
+		return
+	}
+	var (
+		body   []byte
+		reader io.Reader
+	)
+	defer res.Body.Close()
+	encoding := res.Header.Get("Content-Encoding")
+	switch encoding {
+	case "gzip":
+		reader, err = gzip.NewReader(res.Body)
+		if err == nil {
+			body, err = ioutil.ReadAll(reader)
+		}
+	default:
+		body, err = ioutil.ReadAll(res.Body)
+	}
+	if err != nil {
+		return
+	}
+
+	msg.StatusCode = res.StatusCode
+	msg.Header = res.Header
+	msg.Body = body
+	return
+}
+
+// Get HTTP request GET
+func Get(uri string, header map[string]string) (msg Message, err error) {
+	var res *http.Response
+	if res, err = newRequest("GET", uri, header, nil); err != nil {
+		return
+	}
+
+	var (
+		body   []byte
+		reader io.Reader
+	)
+	defer res.Body.Close()
+	encoding := res.Header.Get("Content-Encoding")
+	switch encoding {
+	case "gzip":
+		reader, err = gzip.NewReader(res.Body)
+		if err == nil {
+			body, err = ioutil.ReadAll(reader)
+		}
+	default:
+		body, err = ioutil.ReadAll(res.Body)
+	}
+	if err != nil {
+		return
+	}
+
+	msg.StatusCode = res.StatusCode
+	msg.Header = res.Header
+	msg.Body = body
+
+	return
+}
+
+// SetCookie set http cookie
+func SetCookie(w http.ResponseWriter, name, value, path string, maxAge int) {
+	cookie := &http.Cookie{
+		Name:     name,
+		Value:    value,
+		Path:     path,
+		HttpOnly: false,
+		MaxAge:   maxAge}
+
+	http.SetCookie(w, cookie)
+}

+ 78 - 0
logger.go

@@ -0,0 +1,78 @@
+package util
+
+import (
+	"log"
+	"os"
+)
+
+// Logger logger
+type Logger struct {
+	FP  *os.File
+	Log *log.Logger
+}
+
+var (
+	deflog *Logger
+)
+
+// NewDefaultLogger defult logger
+func NewDefaultLogger(path string) (*Logger, error) {
+	var err error
+	fp := &os.File{}
+	if fp, err = os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666); err != nil {
+		return nil, err
+	}
+
+	deflog = &Logger{}
+	deflog.FP = fp
+	deflog.Log = log.New(fp, "[INFO]", log.Ldate|log.Ltime)
+
+	return deflog, err
+}
+
+// CloseDefaultLogger close
+func CloseDefaultLogger() error {
+	return deflog.FP.Close()
+}
+
+// LogInfo log info
+func LogInfo(v ...interface{}) {
+	deflog.Log.SetPrefix("[INFO]")
+	deflog.Log.Println(v)
+}
+
+// LogDebug log debug
+func LogDebug(v ...interface{}) {
+	deflog.Log.SetPrefix("[DEBUG]")
+	deflog.Log.Println(v)
+}
+
+// NewLogger new
+func NewLogger(path string) (logger *Logger, err error) {
+	fp := &os.File{}
+	if fp, err = os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, os.ModePerm); err != nil {
+		return
+	}
+
+	logger.FP = fp
+	logger.Log = log.New(fp, "[INFO]", log.Ldate|log.Ltime)
+
+	return
+}
+
+// Close close
+func (logger Logger) Close() error {
+	return logger.FP.Close()
+}
+
+// Info log info
+func (logger Logger) Info(v ...interface{}) {
+	logger.Log.SetPrefix("[INFO]")
+	logger.Log.Println(v)
+}
+
+// Debug log debug
+func (logger Logger) Debug(v ...interface{}) {
+	logger.Log.SetPrefix("[DEBUG]")
+	logger.Log.Println(v)
+}

+ 131 - 0
reply.go

@@ -0,0 +1,131 @@
+package util
+
+const (
+	// ErrOk OK
+	ErrOk = 0
+	// ErrNotFound 404 route not found
+	ErrNotFound = 1001
+	// ErrException 500
+	ErrException = 1002
+	// ErrBadRequest 400 route params error
+	ErrBadRequest = 1003
+	// ErrMethodNotAllowed 405
+	ErrMethodNotAllowed = 1004
+	// ErrParamsError 415
+	ErrParamsError = 1005
+	// ErrUnAuthorized 401
+	ErrUnAuthorized = 1006
+	// ErrDataNotFound 404
+	ErrDataNotFound = 1007
+	// ErrNotAllowed 405
+	ErrNotAllowed = 1008
+	// ErrDataExists 400
+	ErrDataExists = 1009
+	// ErrDataValidate 403
+	ErrDataValidate = 1010
+
+	// VarUserAuthorization oauth token
+	VarUserAuthorization = `access_token`
+
+	// HTTPHeaderAuthorization HTTP header Authorization
+	HTTPHeaderAuthorization = `Authorization`
+)
+
+var (
+	statusMessage map[int]string
+)
+
+// ReplyData define API output data
+type ReplyData struct {
+	Status    int               `json:"status" xml:"status"`                     // Status code
+	Message   string            `json:"message" xml:"message"`                   // Message description
+	Errs      map[string]string `json:"errors,omitempty" xml:"errors,omitempty"` // Errs errors
+	PageCount int               `json:"pageCount,omitempty"`
+	Total     int               `json:"total,omitempty" xml:"total,omitempty"` // Total data total
+	List      interface{}       `json:"rows,omitempty" xml:"rows,omitempty"`   // List data list
+	Data      interface{}       `json:"data,omitempty" xml:"data,omitempty"`   // Data data attribute
+}
+
+func init() {
+	statusMessage = make(map[int]string)
+	statusMessage[ErrOk] = `ok`
+	statusMessage[ErrNotFound] = `Not found`
+	statusMessage[ErrException] = `Exception`
+	statusMessage[ErrBadRequest] = `Routing parameter error`
+	statusMessage[ErrMethodNotAllowed] = `Method not allowed`
+	statusMessage[ErrParamsError] = `Parameter or format error`
+	statusMessage[ErrUnAuthorized] = `Not sign in or session has expired`
+	statusMessage[ErrDataNotFound] = `Data not found`
+	statusMessage[ErrNotAllowed] = `No access`
+	statusMessage[ErrDataExists] = `Data exists`
+	statusMessage[ErrDataValidate] = `Data verification failed`
+}
+
+// NewReplyData creates and return ReplyData with status and message
+func NewReplyData(status int) *ReplyData {
+	var (
+		text   string
+		exists bool
+	)
+	if text, exists = statusMessage[status]; !exists {
+		text = `incorrect data type`
+	}
+	return &ReplyData{
+		Status:  status,
+		Message: text,
+	}
+}
+
+// OkReplyData creates and return ReplyData with ok
+func OkReplyData() *ReplyData {
+	message, _ := statusMessage[ErrOk]
+	return &ReplyData{
+		Status:  ErrOk,
+		Message: message,
+	}
+}
+
+// ErrReplyData creates and return ReplyData with error and message
+func ErrReplyData(status int, message string) *ReplyData {
+	text, _ := statusMessage[status]
+	errs := map[string]string{
+		"message": message,
+	}
+	return &ReplyData{
+		Status:  status,
+		Message: text,
+		Errs:    errs,
+	}
+}
+
+// ErrorsReplyData creates and return ReplyData with errors
+func ErrorsReplyData(status int, errors map[string]string) *ReplyData {
+	message, _ := statusMessage[status]
+	return &ReplyData{
+		Status:  status,
+		Message: message,
+		Errs:    errors,
+	}
+}
+
+// RowsReplyData creates and return ReplyData with total and list
+func RowsReplyData(total, pageCount int, rows interface{}) *ReplyData {
+	message, _ := statusMessage[ErrOk]
+	return &ReplyData{
+		Status:    ErrOk,
+		Message:   message,
+		List:      rows,
+		Total:     total,
+		PageCount: pageCount,
+	}
+}
+
+// RowReplyData creates and return ReplyData with attr row
+func RowReplyData(row interface{}) *ReplyData {
+	message, _ := statusMessage[ErrOk]
+	return &ReplyData{
+		Status:  ErrOk,
+		Message: message,
+		Data:    row,
+	}
+}

+ 88 - 0
router.go

@@ -0,0 +1,88 @@
+package util
+
+import (
+	"net/http"
+	"runtime"
+
+	"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.MethodNotAllowed = http.HandlerFunc(notAllowedHandler)
+
+	//router.GET("/api/ver", defaultHandler)
+	//router.GET("/api/health", defaultHandler)
+	//router.Handler("GET", "/metrics", promhttp.Handler())
+
+	return router
+}
+
+func defaultHandler(w http.ResponseWriter, r *http.Request) {
+	ver := map[string]string{
+		"version":  "0.4.0",
+		"comments": "GMS"}
+	WriteJSON(w, RowReplyData(ver))
+}
+
+func panicHandler(w http.ResponseWriter, r *http.Request, err interface{}) {
+	e := err.(runtime.Error)
+
+	WriteJSON(w, ErrReplyData(ErrException, e.Error()))
+}
+
+func notFoundHandler(w http.ResponseWriter, req *http.Request) {
+	WriteJSON(w, ErrReplyData(ErrNotFound, `NotFound`))
+}
+
+func notAllowedHandler(w http.ResponseWriter, req *http.Request) {
+	WriteJSON(w, ErrReplyData(ErrNotAllowed, `Method Not Allowed`))
+}
+
+// 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))
+}

+ 18 - 0
sys/files.go

@@ -0,0 +1,18 @@
+package sys
+
+import (
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+)
+
+// GetCurrentDir get current process directory
+func GetCurrentDir() string {
+	file, _ := exec.LookPath(os.Args[0])
+	path, _ := filepath.Abs(file)
+
+	idx := strings.LastIndex(path, string(os.PathSeparator))
+
+	return path[:idx]
+}

+ 24 - 0
sys/limit.go

@@ -0,0 +1,24 @@
+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
+}

+ 65 - 0
tcp/server.go

@@ -0,0 +1,65 @@
+package tcp
+
+import (
+	"fmt"
+	"log"
+	"net"
+	"os"
+	"os/signal"
+	"syscall"
+)
+
+// NewTCPServer make TCP server
+func NewTCPServer(addr string, cb func(*net.TCPConn)) {
+	var (
+		//buffer bytes.Buffer
+		logger = log.New(os.Stdout, "INFO: ", log.Lshortfile)
+	)
+	// 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)
+	}()
+
+	tcpaddr, err := net.ResolveTCPAddr("tcp", addr)
+	if err != nil {
+		logger.Fatalf("listen error: %v", err)
+		return
+	}
+
+	listen, err := net.ListenTCP("tcp", tcpaddr)
+	if err != nil {
+		logger.Fatalf("listen error: %v", err)
+		return
+	}
+	defer listen.Close()
+
+	go func() {
+		logger.Println("TCP Listen", addr)
+		//log..Log("Protocol", "HTTP", "addr", addr)
+		//errc <- server.ListenAndServe()
+		for {
+			conn, err := listen.Accept()
+			if err != nil {
+				errc <- err
+				log.Fatalf("accept error: %v", err)
+				break
+			}
+
+			tcpConn, ok := conn.(*net.TCPConn)
+			if !ok {
+				//error handle
+				conn.Close()
+				continue
+			}
+			go cb(tcpConn)
+		}
+		logger.Println("Exit server", "Quit")
+	}()
+
+	// Run!
+	//logger.Log("Exit", <-errc)
+	logger.Println("TCP Service Exit", <-errc)
+}

+ 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
+}

+ 39 - 0
types/date.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 15:04:05`
+)
+
+// 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
+}

+ 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()
+}

+ 59 - 0
types/numeric.go

@@ -0,0 +1,59 @@
+package types
+
+import (
+	"database/sql/driver"
+)
+
+// 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 {
+			*n = NullInt(0)
+			return nil
+			//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
+
+// Value implements the driver.Valuer interface,
+// and turns the bytes into a integer for MySQL storage.
+func (n NullFloat) Value() (driver.Value, error) {
+	return NullFloat(n), nil
+}
+
+// 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.(float64)
+		if !ok {
+			*n = NullFloat(0)
+			return nil
+			//return errors.New("bad []byte type assertion (not float64)")
+		}
+
+		*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
+}

+ 1 - 0
url.go

@@ -0,0 +1 @@
+package util

+ 8 - 0
ver.go

@@ -0,0 +1,8 @@
+package util
+
+const (
+	// LibName toolkit name Go micro service
+	LibName = `GMS`
+	// LibVersion toolkit version
+	LibVersion = `0.2.2`
+)

+ 312 - 0
wechat/client.go

@@ -0,0 +1,312 @@
+package wechat
+
+import (
+	"encoding/json"
+	"net/url"
+	"time"
+)
+
+// Client wechat
+type Client struct {
+	AppID          string `json:"appid"`
+	AppSecret      string `json:"appsecret"`
+	Token          string `json:"token"`
+	EncodingAESKey string `json:"encodingaeskey"`
+
+	AccessToken   string
+	LastTokenTime int64
+}
+
+// FormAuthorize get code
+type FormAuthorize struct {
+	Code  string `form:"code"`
+	State string `form:"state"`
+	URL   string `form:"url"`
+}
+
+// FormOpenID openid
+type FormOpenID struct {
+	OpenID string `form:"openid"`
+}
+
+// FormMaterial Material
+type FormMaterial struct {
+	Type   string `form:"type"`
+	Offset int    `form:"offset"`
+	Count  int    `form:"count"`
+}
+
+// UserInfo userinfo
+type UserInfo struct {
+	SubScribe      int    `json:"subscribe"`
+	OpenID         string `json:"openid"`
+	NickName       string `json:"nickname"`
+	Sex            int    `json:"sex"`
+	Language       string `json:"language"`
+	City           string `json:"city"`
+	Province       string `json:"province"`
+	Country        string `json:"country"`
+	HeadImgURL     string `json:"headimgurl"`
+	SubscribeTime  int    `json:"subscribe_time"`
+	Remark         string `json:"remark"`
+	GroupID        int    `json:"groupid"`
+	TagidList      []int  `json:"tagid_list"`
+	SubscribeScene string `json:"subscribe_scene"`
+	QrScene        int    `json:"qr_scene"`
+	QrSceneStr     string `json:"qr_scene_str"`
+}
+
+// List openid list
+type List struct {
+	OpenID []string `json:"openid"`
+}
+
+// UserList user list
+type UserList struct {
+	Total      int    `json:"total"`
+	Count      int    `json:"count"`
+	Data       List   `json:"data"`
+	NextOpenID string `json:"next_openid"`
+}
+
+type newsitem struct {
+	Title              string `json:"title"`
+	Author             string `json:"author"`
+	Digest             string `json:"digest"`
+	Content            string `json:"content"`
+	ContentSourceURL   string `json:"content_source_url"`
+	ThumbMediaID       string `json:"thumb_media_id"`
+	ShowCoverPic       int    `json:"show_cover_pic"`
+	URL                string `json:"url"`
+	ThumbURL           string `json:"thumb_url"`
+	NeedOpenComment    int    `json:"need_open_comment"`
+	OnlyFansCanComment int    `json:"only_fans_can_comment"`
+}
+
+type content struct {
+	NewsItem   []*newsitem `json:"news_item,omitempty"`
+	CreateTime int         `json:"create_time,omitempty"`
+	UpdateTime int         `json:"update_time,omitempty"`
+}
+
+type item struct {
+	MediaID    string   `json:"media_id"`
+	Name       string   `json:"name,omitempty"`
+	UpdateTime int      `json:"update_time"`
+	URL        string   `json:"url,omitempty"`
+	Content    *content `json:"content,omitempty"`
+}
+
+// Material 素材
+type Material struct {
+	TotalCount int    `json:"total_count"`
+	ItemCount  int    `json:"item_count"`
+	Item       []item `json:"item"`
+}
+
+// MiniProgramPage mini
+type MiniProgramPage struct {
+	AppID    string `json:"appid,omitempty"`
+	PagePath string `json:"pagepath,omitempty"`
+}
+
+// ValueColor value color
+type ValueColor struct {
+	Value string `json:"value"`
+	Color string `json:"color,omitempty"`
+}
+
+// TemplateData data
+type TemplateData struct {
+	First    *ValueColor `json:"first"`
+	Keyword1 *ValueColor `json:"keyword1,omitempty"`
+	Keyword2 *ValueColor `json:"keyword2,omitempty"`
+	Keyword3 *ValueColor `json:"keyword3,omitempty"`
+	Keyword4 *ValueColor `json:"keyword4,omitempty"`
+	Keyword5 *ValueColor `json:"keyword5,omitempty"`
+	Remark   *ValueColor `json:"remark,omitempty"`
+}
+
+// TemplateMessage template message
+type TemplateMessage struct {
+	ToUser      string           `json:"touser"`
+	TemplateID  string           `json:"template_id"`
+	URL         string           `json:"url"`
+	MiniProgram *MiniProgramPage `json:"miniprogram,omitempty"`
+	Data        TemplateData     `json:"data"`
+}
+
+func key(appid string) string {
+	return "wechat:client:" + appid
+}
+
+// NewClient new client
+func NewClient(appID, appSecret, token, encodingAESKey string) *Client {
+	key := key(appID)
+	if v, ok := cache.Load(key); ok {
+		return v.(*Client)
+	}
+	c := &Client{AppID: appID, AppSecret: appSecret, Token: token, EncodingAESKey: encodingAESKey}
+	cache.Store(key, c)
+	return c
+}
+
+// getToken get token
+func (wc *Client) getToken() (token string, err error) {
+	now := time.Now().Unix()
+	if wc.LastTokenTime > 0 {
+		if now-wc.LastTokenTime < TokenExpires {
+			token = wc.AccessToken
+			return
+		}
+	}
+	uri := BaseURL + "/cgi-bin/token?"
+
+	args := url.Values{}
+	args.Add("grant_type", "client_credential")
+	args.Add("appid", wc.AppID)
+	args.Add("secret", wc.AppSecret)
+
+	uri += args.Encode()
+	var res Response
+	if res, err = getJSON(uri); err == nil {
+		wc.LastTokenTime = now
+		wc.AccessToken = res.AccessToken
+		token = wc.AccessToken
+		key := key(wc.AppID)
+		cache.Store(key, wc)
+	}
+	return
+}
+
+// GetCodeURL get code 授权获取 OpenID URL
+// /connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
+// 转向到
+// redirect_uri/?code=CODE&state=STATE
+func (wc Client) GetCodeURL(redirectURL, state string) (uri string) {
+	uri = OpenURL + "/connect/oauth2/authorize?"
+	args := url.Values{}
+	args.Add("appid", wc.AppID)
+	args.Add("redirect_uri", redirectURL)
+	args.Add("response_type", "code")
+	args.Add("scope", "snsapi_base")
+	args.Add("state", state)
+
+	uri += args.Encode()
+	uri += "#wechat_redirect"
+
+	return
+}
+
+// GetOpenID 获取 OpenID
+// /sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
+func (wc Client) GetOpenID(code string) (res Response, err error) {
+	uri := BaseURL + "/sns/oauth2/access_token?"
+
+	if wc.AccessToken, err = wc.getToken(); err != nil {
+		return
+	}
+	args := url.Values{}
+	args.Add("access_token", wc.AccessToken)
+	args.Add("appid", wc.AppID)
+	args.Add("secret", wc.AppSecret)
+	args.Add("code", code)
+	args.Add("grant_type", "authorization_code")
+
+	uri += args.Encode()
+	res, err = getJSON(uri)
+
+	return
+}
+
+// GetUserInfo user info
+func (wc Client) GetUserInfo(openid string) (res UserInfo, err error) {
+	uri := BaseURL + "/cgi-bin/user/info?"
+
+	if wc.AccessToken, err = wc.getToken(); err != nil {
+		return
+	}
+	args := url.Values{}
+	args.Add("access_token", wc.AccessToken)
+	args.Add("openid", openid)
+	args.Add("lang", "zh_CN")
+
+	uri += args.Encode()
+	var body []byte
+	body, err = getBody(uri)
+	if err == nil {
+		err = json.Unmarshal(body, &res)
+	}
+
+	return
+}
+
+// GetUserList user list
+func (wc Client) GetUserList(nextOpenID string) (res UserList, err error) {
+	uri := BaseURL + "/cgi-bin/user/get?"
+
+	if wc.AccessToken, err = wc.getToken(); err != nil {
+		return
+	}
+	args := url.Values{}
+	args.Add("access_token", wc.AccessToken)
+	args.Add("next_openid", nextOpenID)
+
+	uri += args.Encode()
+	var body []byte
+	body, err = getBody(uri)
+	if err == nil {
+		err = json.Unmarshal(body, &res)
+	}
+
+	return
+}
+
+// GetMaterial 永久资料
+func (wc Client) GetMaterial(mtype string, offset, count int) (res Material, err error) {
+	uri := BaseURL + "/cgi-bin/material/batchget_material?"
+
+	if wc.AccessToken, err = wc.getToken(); err != nil {
+		return
+	}
+
+	args := url.Values{}
+	data := make(map[string]interface{})
+
+	args.Add("access_token", wc.AccessToken)
+
+	data["type"] = mtype
+	data["offset"] = offset
+	data["count"] = count
+
+	uri += args.Encode()
+	var body []byte
+
+	params, err := json.Marshal(data)
+	body, err = postBody(uri, params)
+	if err == nil {
+		err = json.Unmarshal(body, &res)
+	}
+
+	return
+}
+
+// SendTemplateMessage send template message
+// POST /cgi-bin/message/template/send?access_token=ACCESS_TOKEN
+func (wc Client) SendTemplateMessage(template TemplateMessage) (res Response, err error) {
+	uri := BaseURL + "/cgi-bin/message/template/send?"
+
+	if wc.AccessToken, err = wc.getToken(); err != nil {
+		return
+	}
+	args := url.Values{}
+	args.Add("access_token", wc.AccessToken)
+
+	uri += args.Encode()
+	data, err := json.Marshal(template)
+	if err != nil {
+		return
+	}
+	res, err = postJSON(uri, data)
+	return
+}

+ 288 - 0
wechat/pay.go

@@ -0,0 +1,288 @@
+package wechat
+
+import (
+	"bytes"
+	"encoding/xml"
+	"errors"
+	"fmt"
+
+	"git.chuangxin1.com/cx/util"
+)
+
+const (
+	// WePayHost base host
+	WePayHost = `https://api.mch.weixin.qq.com`
+
+	// WePayURLUnifiedOrder pay
+	WePayURLUnifiedOrder = `/pay/unifiedorder`
+	// WePayURLUnifiedQuery order query
+	WePayURLUnifiedQuery = `/pay/orderquery`
+
+	// WePayTradeTypeJS JSAPI 公众号支付
+	WePayTradeTypeJS = `JSAPI`
+	// WePayTradeTypeNative NATIVE 扫码支付
+	WePayTradeTypeNative = `NATIVE`
+	// WePayTradeTypeAPP APP APP支付
+	WePayTradeTypeAPP = `APP`
+
+	// WeOrderPaySuccess pay ok
+	WeOrderPaySuccess = 0
+)
+
+// WePayConfig WeChat pay configure
+type WePayConfig struct {
+	AppID     string `json:"appid"`
+	MchID     string `json:"mchid"`
+	Key       string `json:"key"`
+	AppSecret string `json:"appsecret"`
+	SSLCert   string `json:"sslcert"`
+	SSLKey    string `json:"sslkey"`
+	NotifyURL string `json:"notify_url"`
+}
+
+// UnifiedOrder https://api.mch.weixin.qq.com/pay/unifiedorder
+type UnifiedOrder struct {
+	XMLName        xml.Name `xml:"xml"`
+	AppID          string   `xml:"appid"`
+	MchID          string   `xml:"mch_id"`
+	Body           string   `xml:"body"`
+	NonceStr       string   `xml:"nonce_str"`
+	NotifyURL      string   `xml:"notify_url"`
+	TradeType      string   `xml:"trade_type"`
+	OpenID         string   `xml:"openid"`
+	SpbillCreateIP string   `xml:"spbill_create_ip"`
+	TimeStart      string   `xml:"time_start"`
+	TotalFee       int      `xml:"total_fee"`
+	OutTradeNo     string   `xml:"out_trade_no"`
+	Attach         string   `xml:"attach"`
+	Sign           string   `xml:"sign"`
+}
+
+// OrderQuery https://api.mch.weixin.qq.com/pay/orderquery
+type OrderQuery struct {
+	XMLName       xml.Name `xml:"xml"`
+	AppID         string   `xml:"appid"`
+	MchID         string   `xml:"mch_id"`
+	NonceStr      string   `xml:"nonce_str"`
+	TransactionID string   `xml:"transaction_id,omitempty"`
+	OutTradeNo    string   `xml:"out_trade_no,omitempty"`
+	Sign          string   `xml:"sign"`
+}
+
+/*
+<xml><appid><![CDATA[wx0e9caf2ab1fcd508]]></appid>
+<attach><![CDATA[邦泰商旅]]></attach>
+<bank_type><![CDATA[CITIC_CREDIT]]></bank_type>
+<cash_fee><![CDATA[349000]]></cash_fee>
+<fee_type><![CDATA[CNY]]></fee_type>
+<is_subscribe><![CDATA[N]]></is_subscribe>
+<mch_id><![CDATA[1340131101]]></mch_id>
+<nonce_str><![CDATA[0gwbd25n61bxkw6m64kj4ovf60i2q2sg]]></nonce_str>
+<openid><![CDATA[o7-PevpkcTqeJOkOF4X7VFhX_SwY]]></openid>
+<out_trade_no><![CDATA[134013110120180709134447]]></out_trade_no>
+<result_code><![CDATA[SUCCESS]]></result_code>
+<return_code><![CDATA[SUCCESS]]></return_code>
+<sign><![CDATA[8E581E734384DB2EA6FC08B1482BCCFC]]></sign>
+<time_end><![CDATA[20180709134502]]></time_end>
+<total_fee>349000</total_fee>
+<trade_type><![CDATA[JSAPI]]></trade_type>
+<transaction_id><![CDATA[4200000120201807096318807841]]></transaction_id>
+</xml>
+
+<xml>
+	<return_code>SUCCESS</return_code>
+	<return_msg></return_msg>
+	<appid>wx84998d72abc5970a</appid>
+	<mch_id>1511843111</mch_id>
+	<nonce_str>1534475690</nonce_str>
+	<sign>27577C258DD809DCE0F8A515233494D1</sign>
+	<result_code>SUCCESS</result_code>
+	<prepay_id></prepay_id>
+	<trade_type>JSAPI</trade_type>
+	<code_url></code_url>
+	<err_code></err_code>
+	<err_code_des></err_code_des>
+	<openid>opCl81S2TyHPx7IJRZxc2yGrom5E</openid>
+	<attach>{&#34;PlaceNo&#34;:&#34;603009&#34;,&#34;VNO&#34;:&#34;&#34;,&#34;Start&#34;:&#34;2018-08-17 11:14:50&#34;</attach>
+	<is_subscribe>Y</is_subscribe>
+	<bank_type>CFT</bank_type>
+	<cash_fee>2</cash_fee>
+	<fee_type>CNY</fee_type>
+	<out_trade_no>1534475690957535612</out_trade_no>
+	<time_end>20180817111506</time_end>
+	<total_fee>2</total_fee>
+	<trade_state></trade_state>
+	<trade_state_desc></trade_state_desc>
+	<transaction_id>4200000159201808174345835044</transaction_id>
+</xml>
+// */
+
+// FormWePayNotify notify
+type FormWePayNotify struct {
+	XMLName  xml.Name `xml:"xml" json:"_,omitempty"`
+	AppID    string   `form:"appid" xml:"appid"`
+	Attach   string   `form:"attach" xml:"attach"`
+	BankType string   `form:"bank_type" xml:"bank_type"`
+	CashFee  int      `form:"cash_fee" xml:"cash_fee"`
+	FeeType  string   `form:"fee_type" xml:"fee_type"`
+
+	MchID       string `form:"mch_id" xml:"mch_id"`
+	IsSubscribe string `form:"is_subscribe" xml:"is_subscribe"`
+	NonceStr    string `form:"nonce_str" xml:"nonce_str"`
+	OpenID      string `form:"openid" xml:"openid"`
+	OutTradeNo  string `form:"out_trade_no" xml:"out_trade_no"`
+
+	ResultCode string `form:"result_code" xml:"result_code"`
+	ReturnMsg  string `form:"return_msg" xml:"return_msg"`
+	ReturnCode string `form:"return_code" xml:"return_code"`
+	ErrCodeDes string `form:"err_code_des" xml:"err_code_des"`
+	ErrCode    string `form:"err_code" xml:"err_code"`
+
+	Sign     string `form:"sign" xml:"sign"`
+	TimeEnd  string `form:"time_end" xml:"time_end"`
+	TotalFee int    `form:"total_fee" xml:"total_fee"`
+
+	TradeType     string `form:"trade_type" xml:"trade_type"`
+	TransactionID string `form:"transaction_id" xml:"transaction_id"`
+}
+
+// WePayReply pay reply
+type WePayReply struct {
+	XMLName        xml.Name `xml:"xml" json:"_,omitempty"`
+	ReturnCode     string   `xml:"return_code"`
+	ReturnMsg      string   `xml:"return_msg"`
+	AppID          string   `xml:"appid"`
+	MchID          string   `xml:"mch_id"`
+	NonceStr       string   `xml:"nonce_str"`
+	Sign           string   `xml:"sign"`
+	ResultCode     string   `xml:"result_code"`
+	PrePayID       string   `xml:"prepay_id"`
+	TradeType      string   `xml:"trade_type"`
+	URL            string   `xml:"code_url"`
+	ErrCode        string   `xml:"err_code"`
+	ErrCodeDes     string   `xml:"err_code_des"`
+	OpenID         string   `xml:"openid"`
+	Attach         string   `xml:"attach"`
+	IsSubscribe    string   `xml:"is_subscribe"`
+	BankType       string   `xml:"bank_type"`
+	CashFee        string   `xml:"cash_fee"`
+	FeeType        string   `xml:"fee_type"`
+	OutTradeNo     string   `xml:"out_trade_no"`
+	TimeEnd        string   `xml:"time_end"`
+	TotalFee       string   `xml:"total_fee"`
+	TradeState     string   `xml:"trade_state"`
+	TradeStateDesc string   `xml:"trade_state_desc"`
+	TransactionID  string   `xml:"transaction_id"`
+}
+
+var (
+	// WePayTradeState 交易状态
+	WePayTradeState = map[string]int{"SUCCESS": 0, "REFUND": 2, "NOTPAY": 3, "CLOSED": 4, "REVOKED": 5, "USERPAYING": 6, "PAYERROR": 7}
+)
+
+// PayCheck check reply
+func PayCheck(reply WePayReply) (ok bool, err error) {
+	//  通信是否正确
+	if reply.ReturnCode == "FAIL" {
+		err = errors.New(reply.ReturnMsg)
+		return
+	}
+	// 交易是否成功
+	if reply.ResultCode == "FAIL" {
+		err = errors.New(reply.ErrCodeDes)
+		return
+	}
+
+	ok = true
+	return
+}
+
+// PayNotifyCheck check reply
+func PayNotifyCheck(reply FormWePayNotify) (ok bool, err error) {
+	//  通信是否正确
+	if reply.ReturnCode == "FAIL" {
+		err = errors.New(reply.ReturnMsg)
+		return
+	}
+	// 交易是否成功
+	if reply.ResultCode == "FAIL" {
+		err = errors.New(reply.ErrCodeDes)
+		return
+	}
+
+	ok = true
+	return
+}
+
+// PayQuery order query
+func PayQuery(cpyid int, config WePayConfig, order OrderQuery) (reply WePayReply, err error) {
+	m := make(map[string]interface{})
+
+	m["appid"] = order.AppID
+	m["mch_id"] = order.MchID
+	m["out_trade_no"] = order.OutTradeNo
+	m["nonce_str"] = order.NonceStr
+	//m["transaction_id"] = order.TransactionID
+
+	var (
+		data []byte
+	)
+
+	order.Sign = Sign(m, config.Key)
+	data, err = xml.Marshal(order)
+	if err != nil {
+		return
+	}
+
+	reply, err = post(WePayHost+WePayURLUnifiedQuery, data)
+	return
+}
+
+// Pay pay
+func Pay(config WePayConfig, order UnifiedOrder) (reply WePayReply, err error) {
+	m := make(map[string]interface{})
+
+	m["appid"] = order.AppID
+	m["body"] = order.Body
+	m["mch_id"] = order.MchID
+	m["notify_url"] = order.NotifyURL
+	m["trade_type"] = order.TradeType
+	m["spbill_create_ip"] = order.SpbillCreateIP
+	m["total_fee"] = order.TotalFee
+	m["out_trade_no"] = order.OutTradeNo
+	m["nonce_str"] = order.NonceStr
+	m["attach"] = order.Attach
+	m["time_start"] = order.TimeStart
+	m["openid"] = order.OpenID
+
+	var data []byte
+
+	order.Sign = Sign(m, config.Key)
+	if data, err = xml.Marshal(order); err != nil {
+		return
+	}
+
+	reply, err = post(WePayHost+WePayURLUnifiedOrder, data)
+	return
+}
+
+func post(url string, data []byte) (reply WePayReply, err error) {
+	var (
+		msg     util.Message
+		headers = make(map[string]string)
+	)
+
+	headers["Accept"] = "application/xml"
+	headers["Content-Type"] = "application/xml; charset=utf-8"
+	msg, err = util.Post(url, headers, bytes.NewReader(data))
+	if err != nil {
+		return
+	}
+
+	fmt.Println("************Response************")
+	fmt.Println(string(msg.Body))
+	fmt.Println("************ResponseEnd************")
+	err = xml.Unmarshal(msg.Body, &reply)
+
+	return
+}

+ 28 - 0
wechat/server.go

@@ -0,0 +1,28 @@
+package wechat
+
+// Server wechat
+type Server struct {
+	AppID          string `json:"appid"`
+	AppSecret      string `json:"appsecret"`
+	Token          string `json:"token"`
+	EncodingAESKey string `json:"encodingaeskey"`
+}
+
+// NewServer new Server
+func NewServer(appID, appSecret, token, encodingAESKey string) *Server {
+	key := "wechat:server:" + appID
+	if v, ok := cache.Load(key); ok {
+		return v.(*Server)
+	}
+	s := &Server{AppID: appID, AppSecret: appSecret, Token: token, EncodingAESKey: encodingAESKey}
+	cache.Store(key, s)
+	return s
+}
+
+// Echo notify echo GET
+func (wc Server) Echo(s FormSignature) (data string, err error) {
+	if ok := makeSignature(wc.Token, s.Signature, s.TimeStamp, s.Nonce); ok {
+		data = s.Echostr
+	}
+	return
+}

+ 222 - 0
wechat/wechat.go

@@ -0,0 +1,222 @@
+package wechat
+
+import (
+	"bytes"
+	"encoding/json"
+	"encoding/xml"
+	"errors"
+	"fmt"
+	"sort"
+	"strings"
+	"sync"
+
+	"git.chuangxin1.com/cx/util"
+)
+
+const (
+	// TokenExpires token expires time 1 hours
+	TokenExpires = 60 * 60
+	// BaseURL api host
+	BaseURL = `https://api.weixin.qq.com`
+	// OpenURL open host
+	OpenURL = `https://open.weixin.qq.com`
+)
+
+// ResponseMsg response
+type ResponseMsg struct {
+	ErrCode int    `json:"errcode"`
+	ErrMsg  string `json:"errmsg"`
+}
+
+// Response response
+type Response struct {
+	ErrCode int    `json:"errcode"`
+	ErrMsg  string `json:"errmsg"`
+
+	// token
+	AccessToken string `json:"access_token"`
+	ExpiresIn   int    `json:"expires_in"`
+
+	// openid
+	RefreshToken string `json:"refresh_token"`
+	OpenID       string `json:"openid"`
+	Scope        string `json:"scope"`
+
+	// user info
+	NickName   string `json:"nickname"`
+	Sex        string `json:"sex"`
+	Province   string `json:"province"`
+	City       string `json:"city"`
+	Country    string `json:"country"`
+	HeadImgURL string `json:"headimgurl"`
+	Privilege  string `json:"privilege"`
+	UnionID    string `json:"unionid"`
+
+	// template message
+	MsgID int64 `json:"msgid"`
+}
+
+// FormSignature signature
+type FormSignature struct {
+	TimeStamp string `form:"timestamp" json:"timestamp"`
+	Nonce     string `form:"nonce" json:"nonce"`
+	Signature string `form:"signature" json:"signature"`
+	Echostr   string `form:"echostr" json:"echostr"`
+}
+
+// Message message
+type Message struct {
+	XMLName      xml.Name `xml:"xml"`
+	ToUserName   string   `xml:"ToUserName" json:"ToUserName"`
+	FromUserName string   `xml:"FromUserName" json:"FromUserName"`
+	CreateTime   int32    `xml:"CreateTime" json:"CreateTime"`
+	MsgType      string   `xml:"MsgType" json:"MsgType"`
+	MsgID        int64    `xml:"MsgId" json:"MsgId"`
+}
+
+// EventTemplateReply event reply
+type EventTemplateReply struct {
+	XMLName      xml.Name `xml:"xml"`
+	ToUserName   string   `xml:"ToUserName"`
+	FromUserName string   `xml:"FromUserName"`
+	CreateTime   string   `xml:"CreateTime"`
+	MsgType      string   `xml:"MsgType"`
+	Event        string   `xml:"Event"`
+	MsgID        string   `xml:"MsgID"`
+	Status       string   `xml:"Status"`
+}
+
+var (
+	cache sync.Map
+)
+
+// Sign 微信计算签名的函数
+func Sign(req map[string]interface{}, key string) (sign string) {
+	keys := make([]string, 0)
+
+	for k := range req {
+		keys = append(keys, k)
+	}
+
+	sort.Strings(keys)
+
+	var s string
+	for _, k := range keys {
+		value := fmt.Sprintf("%v", req[k])
+		if value != "" {
+			s = s + k + "=" + value + "&"
+		}
+	}
+
+	if key != "" {
+		s = s + "key=" + key
+	}
+
+	sign = strings.ToUpper(util.MD5(s))
+
+	return
+}
+
+func makeSignature(token, signature, timestamp, nonce string) bool {
+	sl := []string{token, timestamp, nonce}
+	sort.Strings(sl)
+	return strings.Compare(signature, util.SHA1(strings.Join(sl, ""))) == 0
+}
+
+func checkErrorWithBody(msg util.Message) (body []byte, err error) {
+	if msg.StatusCode == 200 {
+		var res ResponseMsg
+		err = json.Unmarshal(msg.Body, &res)
+		if err == nil {
+			if res.ErrCode != 0 {
+				err = errors.New(res.ErrMsg)
+			} else {
+				body = msg.Body
+			}
+		}
+	} else {
+		err = errors.New(`HTTP StatusCode not 200`)
+	}
+	return
+}
+
+func checkError(msg util.Message) (res Response, err error) {
+	if msg.StatusCode == 200 {
+		err = json.Unmarshal(msg.Body, &res)
+		if err == nil {
+			if res.ErrCode != 0 {
+				err = errors.New(res.ErrMsg)
+			}
+		}
+	} else {
+		err = errors.New(`HTTP StatusCode not 200`)
+	}
+	return
+}
+
+func getJSON(uri string) (res Response, err error) {
+	var msg util.Message
+	if msg, err = util.Get(uri, map[string]string{}); err != nil {
+		return
+	}
+	res, err = checkError(msg)
+	return
+}
+
+func getBody(uri string) (body []byte, err error) {
+	var msg util.Message
+	if msg, err = util.Get(uri, map[string]string{}); err != nil {
+		return
+	}
+	body, err = checkErrorWithBody(msg)
+	return
+}
+
+func postXML(url string, data []byte) (res Response, err error) {
+	var (
+		msg     util.Message
+		headers = make(map[string]string)
+	)
+
+	headers["Accept"] = "application/json"
+	headers["Content-Type"] = "application/xml; charset=utf-8"
+	msg, err = util.Post(url, headers, bytes.NewReader(data))
+	if err != nil {
+		return
+	}
+	res, err = checkError(msg)
+	return
+}
+
+func postJSON(url string, data []byte) (res Response, err error) {
+	var (
+		msg     util.Message
+		headers = make(map[string]string)
+	)
+
+	headers["Accept"] = "application/json"
+	headers["Content-Type"] = "application/json; charset=utf-8"
+	msg, err = util.Post(url, headers, bytes.NewReader(data))
+	if err != nil {
+		return
+	}
+	res, err = checkError(msg)
+	return
+}
+
+func postBody(uri string, data []byte) (body []byte, err error) {
+	var (
+		msg     util.Message
+		headers = make(map[string]string)
+	)
+
+	headers["Accept"] = "application/json"
+	headers["Content-Type"] = "application/json; charset=utf-8"
+	msg, err = util.Post(uri, headers, bytes.NewReader(data))
+	if err != nil {
+		return
+	}
+	body, err = checkErrorWithBody(msg)
+
+	return
+}