Explorar o código

Merge pull request #198 from silenceper/develop

增加小程序云开发http api
silenceper %!s(int64=6) %!d(string=hai) anos
pai
achega
cafb84d6da
Modificáronse 14 ficheiros con 757 adicións e 153 borrados
  1. 2 1
      .gitignore
  2. 1 0
      README.md
  3. 21 0
      go.mod
  4. 44 0
      go.sum
  5. 1 1
      template/template.go
  6. 32 0
      tcb/README.md
  7. 35 0
      tcb/cloudfunction.go
  8. 418 0
      tcb/database.go
  9. 134 0
      tcb/file.go
  10. 16 0
      tcb/tcb.go
  11. 26 0
      util/error.go
  12. 17 3
      util/http.go
  13. 0 144
      vendor/vendor.json
  14. 10 4
      wechat.go

+ 2 - 1
.gitignore

@@ -24,5 +24,6 @@ _testmain.go
 *.prof
 .DS_Store
 .vscode/
-vendor/*/
+vendor
 .idea/
+examples/tcb/*

+ 1 - 0
README.md

@@ -97,6 +97,7 @@ Cache主要用来保存全局access_token以及js-sdk中的ticket:
 	- 获取js-sdk配置
 - [素材管理](#素材管理)
 - [小程序开发](#小程序开发)
+- [小程序-云开发](./tcb)
 
 ## 消息管理
 

+ 21 - 0
go.mod

@@ -0,0 +1,21 @@
+module github.com/silenceper/wechat
+
+go 1.13
+
+require (
+	github.com/astaxie/beego v1.7.1
+	github.com/bradfitz/gomemcache v0.0.0-20160117192205-fb1f79c6b65a
+	github.com/gin-gonic/gin v1.1.4
+	github.com/golang/protobuf v0.0.0-20161117033126-8ee79997227b // indirect
+	github.com/gomodule/redigo v2.0.1-0.20180627144507-2cd21d9966bf+incompatible
+	github.com/kr/pretty v0.1.0
+	github.com/manucorporat/sse v0.0.0-20160126180136-ee05b128a739 // indirect
+	github.com/mattn/go-isatty v0.0.0-20161123143637-30a891c33c7c // indirect
+	github.com/stretchr/testify v1.4.0 // indirect
+	golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
+	golang.org/x/net v0.0.0-20191125084936-ffdde1057850 // indirect
+	golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
+	gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
+	gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
+	gopkg.in/go-playground/validator.v8 v8.18.1 // indirect
+)

+ 44 - 0
go.sum

@@ -0,0 +1,44 @@
+github.com/astaxie/beego v1.7.1 h1:TuqX4F9e3ujVEycudgWrwUj11WMppLZyunJKIBoxTFw=
+github.com/astaxie/beego v1.7.1/go.mod h1:0R4++1tUqERR0WYFWdfkcrsyoVBCG4DgpDGokT3yb+U=
+github.com/bradfitz/gomemcache v0.0.0-20160117192205-fb1f79c6b65a h1:k5TuEkqEYCRs8+66WdOkswWOj+L/YbP5ruainvn94wg=
+github.com/bradfitz/gomemcache v0.0.0-20160117192205-fb1f79c6b65a/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/gin-gonic/gin v1.1.4 h1:XLaCFbU39SSGRQrEeP7Z7mM3lvRqC4vE5tEaVdLDdSE=
+github.com/gin-gonic/gin v1.1.4/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
+github.com/golang/protobuf v0.0.0-20161117033126-8ee79997227b h1:fE/yi9pibxGEc0gSJuEShcsBXE2d5FW3OudsjE9tKzQ=
+github.com/golang/protobuf v0.0.0-20161117033126-8ee79997227b/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/gomodule/redigo v2.0.1-0.20180627144507-2cd21d9966bf+incompatible h1:QJ4V3LjaRe/6NKoaaj2QzQZcezt5gNXdPv0axxS4VNA=
+github.com/gomodule/redigo v2.0.1-0.20180627144507-2cd21d9966bf+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/manucorporat/sse v0.0.0-20160126180136-ee05b128a739 h1:ykXz+pRRTibcSjG1yRhpdSHInF8yZY/mfn+Rz2Nd1rE=
+github.com/manucorporat/sse v0.0.0-20160126180136-ee05b128a739/go.mod h1:zUx1mhth20V3VKgL5jbd1BSQcW4Fy6Qs4PZvQwRFwzM=
+github.com/mattn/go-isatty v0.0.0-20161123143637-30a891c33c7c h1:YHHK/dEmr2Jo1cWD1VMB2waEeHJhHFp3CEylwWy/VcY=
+github.com/mattn/go-isatty v0.0.0-20161123143637-30a891c33c7c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/net v0.0.0-20191125084936-ffdde1057850 h1:Vq85/r8R9IdcUHmZ0/nQlUg1v15rzvQ2sHdnZAj/x7s=
+golang.org/x/net v0.0.0-20191125084936-ffdde1057850/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
+gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
+gopkg.in/go-playground/validator.v8 v8.18.1 h1:F8SLY5Vqesjs1nI1EL4qmF1PQZ1sitsmq0rPYXLyfGU=
+gopkg.in/go-playground/validator.v8 v8.18.1/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

+ 1 - 1
template/template.go

@@ -1,4 +1,4 @@
-package template
+package message
 
 import (
 	"encoding/json"

+ 32 - 0
tcb/README.md

@@ -0,0 +1,32 @@
+# 小程序-云开发 SDK
+
+Tencent Cloud Base [文档](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/)
+
+## 使用说明
+
+**初始化配置**
+
+```golang
+//使用memcache保存access_token,也可选择redis或自定义cache
+memCache=cache.NewMemcache("127.0.0.1:11211")
+
+//配置小程序参数
+config := &wechat.Config{
+    AppID:     "your app id",
+    AppSecret: "your app secret",
+    Cache:     memCache,
+}
+wc := wechat.NewWechat(config)
+wcTcb := wc.GetTcb()
+```
+
+### 举例
+#### 触发云函数
+```golang
+res, err := wcTcb.UploadFile("test-6ku2s", "golang.png")
+if err != nil {
+    panic(err)
+}
+```
+
+更多使用方法参考[GODOC](https://godoc.org/github.com/silenceper/wechat/tcb)

+ 35 - 0
tcb/cloudfunction.go

@@ -0,0 +1,35 @@
+package tcb
+
+import (
+	"fmt"
+
+	"github.com/silenceper/wechat/util"
+)
+
+const (
+	//触发云函数
+	invokeCloudFunctionURL = "https://api.weixin.qq.com/tcb/invokecloudfunction"
+)
+
+//InvokeCloudFunctionRes 云函数调用返回结果
+type InvokeCloudFunctionRes struct {
+	util.CommonError
+	RespData string `json:"resp_data"` //云函数返回的buffer
+}
+
+//InvokeCloudFunction 云函数调用
+//reference:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/functions/invokeCloudFunction.html
+func (tcb *Tcb) InvokeCloudFunction(env, name, args string) (*InvokeCloudFunctionRes, error) {
+	accessToken, err := tcb.GetAccessToken()
+	if err != nil {
+		return nil, err
+	}
+	uri := fmt.Sprintf("%s?access_token=%s&env=%s&name=%s", invokeCloudFunctionURL, accessToken, env, name)
+	response, err := util.HTTPPost(uri, args)
+	if err != nil {
+		return nil, err
+	}
+	invokeCloudFunctionRes := &InvokeCloudFunctionRes{}
+	err = util.DecodeWithError(response, invokeCloudFunctionRes, "InvokeCloudFunction")
+	return invokeCloudFunctionRes, err
+}

+ 418 - 0
tcb/database.go

@@ -0,0 +1,418 @@
+package tcb
+
+import (
+	"fmt"
+
+	"github.com/silenceper/wechat/util"
+)
+
+const (
+	//数据库导入
+	databaseMigrateImportURL = "https://api.weixin.qq.com/tcb/databasemigrateimport"
+	//数据库导出
+	databaseMigrateExportURL = "https://api.weixin.qq.com/tcb/databasemigrateexport"
+	//数据库迁移状态查询
+	databaseMigrateQueryInfoURL = "https://api.weixin.qq.com/tcb/databasemigratequeryinfo"
+	//变更数据库索引
+	updateIndexURL = "https://api.weixin.qq.com/tcb/updateindex"
+	//新增集合
+	databaseCollectionAddURL = "https://api.weixin.qq.com/tcb/databasecollectionadd"
+	//删除集合
+	databaseCollectionDeleteURL = "https://api.weixin.qq.com/tcb/databasecollectiondelete"
+	//获取特定云环境下集合信息
+	databaseCollectionGetURL = "https://api.weixin.qq.com/tcb/databasecollectionget"
+	//数据库插入记录
+	databaseAddURL = "https://api.weixin.qq.com/tcb/databaseadd"
+	//数据库删除记录
+	databaseDeleteURL = "https://api.weixin.qq.com/tcb/databasedelete"
+	//数据库更新记录
+	databaseUpdateURL = "https://api.weixin.qq.com/tcb/databaseupdate"
+	//数据库查询记录
+	databaseQueryURL = "https://api.weixin.qq.com/tcb/databasequery"
+	//统计集合记录数或统计查询语句对应的结果记录数
+	databaseCountURL = "https://api.weixin.qq.com/tcb/databasecount"
+
+	//ConflictModeInster 冲突处理模式 插入
+	ConflictModeInster ConflictMode = 1
+	//ConflictModeUpsert 冲突处理模式 更新
+	ConflictModeUpsert ConflictMode = 2
+
+	//FileTypeJSON 的合法值 json
+	FileTypeJSON FileType = 1
+	//FileTypeCsv 的合法值 csv
+	FileTypeCsv FileType = 2
+)
+
+//ConflictMode 冲突处理模式
+type ConflictMode int
+
+//FileType 文件上传和导出的允许文件类型
+type FileType int
+
+//ValidDirections 合法的direction值
+var ValidDirections = []string{"1", "-1", "2dsphere"}
+
+//DatabaseMigrateExportReq 数据库出 请求参数
+type DatabaseMigrateExportReq struct {
+	Env      string   `json:"env,omitempty"`       //云环境ID
+	FilePath string   `json:"file_path,omitempty"` //导出文件路径(导入文件需先上传到同环境的存储中,可使用开发者工具或 HTTP API的上传文件 API上传)
+	FileType FileType `json:"file_type,omitempty"` //导出文件类型,文件格式参考数据库导入指引中的文件格式部分  1:json 2:csv
+	Query    string   `json:"query,omitempty"`     //导出条件
+}
+
+//DatabaseMigrateExportRes 数据库导出 返回结果
+type DatabaseMigrateExportRes struct {
+	util.CommonError
+	JobID int64 `json:"job_id"` //导出任务ID,可使用数据库迁移进度查询 API 查询导入进度及结果
+}
+
+//DatabaseMigrateImportReq 数据库导入 请求参数
+type DatabaseMigrateImportReq struct {
+	Env            string       `json:"env,omitempty"`             //云环境ID
+	CollectionName string       `json:"collection_name,omitempty"` //集合名称
+	FilePath       string       `json:"file_path,omitempty"`       //导出文件路径(文件会导出到同环境的云存储中,可使用获取下载链接 API 获取下载链接)
+	FileType       FileType     `json:"file_type,omitempty"`       //导入文件类型,文件格式参考数据库导入指引中的文件格式部分  1:json 2:csv
+	StopOnError    bool         `json:"stop_on_error,omitempty"`   //是否在遇到错误时停止导入
+	ConflictMode   ConflictMode `json:"conflict_mode,omitempty"`   //冲突处理模式  1:inster 2:UPSERT
+}
+
+//DatabaseMigrateImportRes 数据库导入 返回结果
+type DatabaseMigrateImportRes struct {
+	util.CommonError
+	JobID int64 `json:"job_id"` //导入任务ID,可使用数据库迁移进度查询 API 查询导入进度及结果
+}
+
+//DatabaseMigrateQueryInfoRes 数据库迁移状态查询
+type DatabaseMigrateQueryInfoRes struct {
+	util.CommonError
+	Status        string `json:"status"`         //导出状态
+	RecordSuccess int64  `json:"record_success"` //导出成功记录数
+	RecordFail    int64  `json:"record_fail"`    //导出失败记录数
+	ErrMsg        string `json:"err_msg"`        //导出错误信息
+	FileURL       string `json:"file_url"`       //导出文件下载地址
+}
+
+//UpdateIndexReq 变更数据库索引 请求参数
+type UpdateIndexReq struct {
+	Env            string        `json:"env,omitempty"`             //云环境ID
+	CollectionName string        `json:"collection_name,omitempty"` //集合名称
+	CreateIndexes  []CreateIndex `json:"create_indexes,omitempty"`  //新增索引
+	DropIndexes    []DropIndex   `json:"drop_indexes,omitempty"`    //删除索引
+}
+
+//CreateIndex 新增索引
+type CreateIndex struct {
+	Name   string           `json:"name,omitempty"`   //索引名
+	Unique bool             `json:"unique,omitempty"` //是否唯一
+	Keys   []CreateIndexKey `json:"keys,omitempty"`   //索引字段
+}
+
+//CreateIndexKey create index key
+type CreateIndexKey struct {
+	Name      string `json:"name,omitempty"`      //字段名
+	Direction string `json:"direction,omitempty"` //字段排序
+}
+
+//DropIndex 删除索引
+type DropIndex struct {
+	Name string `json:"name,omitempty"`
+}
+
+//DatabaseCollectionReq 新增/删除集合请求参数
+type DatabaseCollectionReq struct {
+	Env            string `json:"env,omitempty"`             //云环境ID
+	CollectionName string `json:"collection_name,omitempty"` //集合名称
+}
+
+//DatabaseCollectionGetReq 获取特定云环境下集合信息请求
+type DatabaseCollectionGetReq struct {
+	Env    string `json:"env,omitempty"`    //云环境ID
+	Limit  int64  `json:"limit,omitempty"`  //获取数量限制
+	Offset int64  `json:"offset,omitempty"` //偏移量
+}
+
+//DatabaseCollectionGetRes 获取特定云环境下集合信息结果
+type DatabaseCollectionGetRes struct {
+	util.CommonError
+	Pager struct {
+		Limit  int64 `json:"limit"`  //单次查询限制
+		Offset int64 `json:"offset"` //偏移量
+		Total  int64 `json:"total"`  //符合查询条件的记录总数
+	} `json:"pager"`
+	Collections []struct {
+		Name       string `json:"name"`        //集合名
+		Count      int64  `json:"count"`       //表中文档数量
+		Size       int64  `json:"size"`        //表的大小(即表中文档总大小),单位:字节
+		IndexCount int64  `json:"index_count"` //索引数量
+		IndexSize  int64  `json:"index_size"`  //索引占用大小,单位:字节
+	} `json:"collections"`
+}
+
+//DatabaseReq 数据库插入/删除/更新/查询/统计记录请求参数
+type DatabaseReq struct {
+	Env   string `json:"env,omitempty"`   //云环境ID
+	Query string `json:"query,omitempty"` //数据库操作语句
+}
+
+//DatabaseAddRes 数据库插入记录返回结果
+type DatabaseAddRes struct {
+	util.CommonError
+	IDList []string `json:"id_list"` //插入成功的数据集合主键_id。
+}
+
+//DatabaseDeleteRes 数据库删除记录返回结果
+type DatabaseDeleteRes struct {
+	util.CommonError
+	Deleted int64 `json:"deleted"` //删除记录数量
+}
+
+//DatabaseUpdateRes 数据库更新记录返回结果
+type DatabaseUpdateRes struct {
+	util.CommonError
+	Matched  int64  `json:"matched"`  //更新条件匹配到的结果数
+	Modified int64  `json:"modified"` //修改的记录数,注意:使用set操作新插入的数据不计入修改数目
+	ID       string `json:"id"`
+}
+
+//DatabaseQueryRes 数据库查询记录 返回结果
+type DatabaseQueryRes struct {
+	util.CommonError
+	Pager struct {
+		Limit  int64 `json:"limit"`  //单次查询限制
+		Offset int64 `json:"offset"` //偏移量
+		Total  int64 `json:"total"`  //符合查询条件的记录总数
+	} `json:"pager"`
+	Data []string `json:"data"`
+}
+
+//DatabaseCountRes 统计集合记录数或统计查询语句对应的结果记录数 返回结果
+type DatabaseCountRes struct {
+	util.CommonError
+	Count int64 `json:"count"` //记录数量
+}
+
+//DatabaseMigrateImport 数据库导入
+//reference:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseMigrateImport.html
+func (tcb *Tcb) DatabaseMigrateImport(req *DatabaseMigrateImportReq) (*DatabaseMigrateImportRes, error) {
+	accessToken, err := tcb.GetAccessToken()
+	if err != nil {
+		return nil, err
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", databaseMigrateImportURL, accessToken)
+	response, err := util.PostJSON(uri, req)
+	if err != nil {
+		return nil, err
+	}
+	databaseMigrateImportRes := &DatabaseMigrateImportRes{}
+	err = util.DecodeWithError(response, databaseMigrateImportRes, "DatabaseMigrateImport")
+	return databaseMigrateImportRes, err
+}
+
+//DatabaseMigrateExport 数据库导出
+//reference:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseMigrateExport.html
+func (tcb *Tcb) DatabaseMigrateExport(req *DatabaseMigrateExportReq) (*DatabaseMigrateExportRes, error) {
+	accessToken, err := tcb.GetAccessToken()
+	if err != nil {
+		return nil, err
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", databaseMigrateExportURL, accessToken)
+	response, err := util.PostJSON(uri, req)
+	if err != nil {
+		return nil, err
+	}
+	databaseMigrateExportRes := &DatabaseMigrateExportRes{}
+	err = util.DecodeWithError(response, databaseMigrateExportRes, "DatabaseMigrateExport")
+	return databaseMigrateExportRes, err
+}
+
+//DatabaseMigrateQueryInfo 数据库迁移状态查询
+//reference:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseMigrateQueryInfo.html
+func (tcb *Tcb) DatabaseMigrateQueryInfo(env string, jobID int64) (*DatabaseMigrateQueryInfoRes, error) {
+	accessToken, err := tcb.GetAccessToken()
+	if err != nil {
+		return nil, err
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", databaseMigrateQueryInfoURL, accessToken)
+	response, err := util.PostJSON(uri, map[string]interface{}{
+		"env":    env,
+		"job_id": jobID,
+	})
+	if err != nil {
+		return nil, err
+	}
+	databaseMigrateQueryInfoRes := &DatabaseMigrateQueryInfoRes{}
+	err = util.DecodeWithError(response, databaseMigrateQueryInfoRes, "DatabaseMigrateQueryInfo")
+	return databaseMigrateQueryInfoRes, err
+}
+
+//UpdateIndex 变更数据库索引
+//https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/updateIndex.html
+func (tcb *Tcb) UpdateIndex(req *UpdateIndexReq) error {
+	accessToken, err := tcb.GetAccessToken()
+	if err != nil {
+		return err
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", updateIndexURL, accessToken)
+	response, err := util.PostJSON(uri, req)
+	if err != nil {
+		return err
+	}
+	return util.DecodeWithCommonError(response, "UpdateIndex")
+}
+
+//DatabaseCollectionAdd 新增集合
+//reference:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseCollectionAdd.html
+func (tcb *Tcb) DatabaseCollectionAdd(env, collectionName string) error {
+	accessToken, err := tcb.GetAccessToken()
+	if err != nil {
+		return err
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", databaseCollectionAddURL, accessToken)
+	response, err := util.PostJSON(uri, &DatabaseCollectionReq{
+		Env:            env,
+		CollectionName: collectionName,
+	})
+	if err != nil {
+		return err
+	}
+	return util.DecodeWithCommonError(response, "DatabaseCollectionAdd")
+}
+
+//DatabaseCollectionDelete 删除集合
+//reference:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseCollectionDelete.html
+func (tcb *Tcb) DatabaseCollectionDelete(env, collectionName string) error {
+	accessToken, err := tcb.GetAccessToken()
+	if err != nil {
+		return err
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", databaseCollectionDeleteURL, accessToken)
+	response, err := util.PostJSON(uri, &DatabaseCollectionReq{
+		Env:            env,
+		CollectionName: collectionName,
+	})
+	if err != nil {
+		return err
+	}
+	return util.DecodeWithCommonError(response, "DatabaseCollectionDelete")
+}
+
+//DatabaseCollectionGet 获取特定云环境下集合信息
+//reference:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseCollectionGet.html
+func (tcb *Tcb) DatabaseCollectionGet(env string, limit, offset int64) (*DatabaseCollectionGetRes, error) {
+	accessToken, err := tcb.GetAccessToken()
+	if err != nil {
+		return nil, err
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", databaseCollectionGetURL, accessToken)
+	response, err := util.PostJSON(uri, &DatabaseCollectionGetReq{
+		Env:    env,
+		Limit:  limit,
+		Offset: offset,
+	})
+	if err != nil {
+		return nil, err
+	}
+	databaseCollectionGetRes := &DatabaseCollectionGetRes{}
+	err = util.DecodeWithError(response, databaseCollectionGetRes, "DatabaseCollectionGet")
+	return databaseCollectionGetRes, err
+}
+
+//DatabaseAdd 数据库插入记录
+//reference:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseAdd.html
+func (tcb *Tcb) DatabaseAdd(env, query string) (*DatabaseAddRes, error) {
+	accessToken, err := tcb.GetAccessToken()
+	if err != nil {
+		return nil, err
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", databaseAddURL, accessToken)
+	response, err := util.PostJSON(uri, &DatabaseReq{
+		Env:   env,
+		Query: query,
+	})
+	if err != nil {
+		return nil, err
+	}
+	databaseAddRes := &DatabaseAddRes{}
+	err = util.DecodeWithError(response, databaseAddRes, "DatabaseAdd")
+	return databaseAddRes, err
+}
+
+//DatabaseDelete 数据库插入记录
+//reference:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseDelete.html
+func (tcb *Tcb) DatabaseDelete(env, query string) (*DatabaseDeleteRes, error) {
+	accessToken, err := tcb.GetAccessToken()
+	if err != nil {
+		return nil, err
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", databaseDeleteURL, accessToken)
+	response, err := util.PostJSON(uri, &DatabaseReq{
+		Env:   env,
+		Query: query,
+	})
+	if err != nil {
+		return nil, err
+	}
+	databaseDeleteRes := &DatabaseDeleteRes{}
+	err = util.DecodeWithError(response, databaseDeleteRes, "DatabaseDelete")
+	return databaseDeleteRes, err
+}
+
+//DatabaseUpdate 数据库插入记录
+//reference:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseUpdate.html
+func (tcb *Tcb) DatabaseUpdate(env, query string) (*DatabaseUpdateRes, error) {
+	accessToken, err := tcb.GetAccessToken()
+	if err != nil {
+		return nil, err
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", databaseUpdateURL, accessToken)
+	response, err := util.PostJSON(uri, &DatabaseReq{
+		Env:   env,
+		Query: query,
+	})
+	if err != nil {
+		return nil, err
+	}
+	databaseUpdateRes := &DatabaseUpdateRes{}
+	err = util.DecodeWithError(response, databaseUpdateRes, "DatabaseUpdate")
+	return databaseUpdateRes, err
+}
+
+//DatabaseQuery 数据库查询记录
+//reference:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseQuery.html
+func (tcb *Tcb) DatabaseQuery(env, query string) (*DatabaseQueryRes, error) {
+	accessToken, err := tcb.GetAccessToken()
+	if err != nil {
+		return nil, err
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", databaseQueryURL, accessToken)
+	response, err := util.PostJSON(uri, &DatabaseReq{
+		Env:   env,
+		Query: query,
+	})
+	if err != nil {
+		return nil, err
+	}
+	databaseQueryRes := &DatabaseQueryRes{}
+	err = util.DecodeWithError(response, databaseQueryRes, "DatabaseQuery")
+	return databaseQueryRes, err
+}
+
+//DatabaseCount 统计集合记录数或统计查询语句对应的结果记录数
+//reference:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseCount.html
+func (tcb *Tcb) DatabaseCount(env, query string) (*DatabaseCountRes, error) {
+	accessToken, err := tcb.GetAccessToken()
+	if err != nil {
+		return nil, err
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", databaseCountURL, accessToken)
+	response, err := util.PostJSON(uri, &DatabaseReq{
+		Env:   env,
+		Query: query,
+	})
+	if err != nil {
+		return nil, err
+	}
+	databaseCountRes := &DatabaseCountRes{}
+	err = util.DecodeWithError(response, databaseCountRes, "DatabaseCount")
+	return databaseCountRes, err
+}

+ 134 - 0
tcb/file.go

@@ -0,0 +1,134 @@
+package tcb
+
+import (
+	"fmt"
+
+	"github.com/silenceper/wechat/util"
+)
+
+const (
+	//获取文件上传链接
+	uploadFilePathURL = "https://api.weixin.qq.com/tcb/uploadfile"
+	//获取文件下载链接
+	batchDownloadFileURL = "https://api.weixin.qq.com/tcb/batchdownloadfile"
+	//删除文件链接
+	batchDeleteFileURL = "https://api.weixin.qq.com/tcb/batchdeletefile"
+)
+
+//UploadFileReq 上传文件请求值
+type UploadFileReq struct {
+	Env  string `json:"env,omitempty"`
+	Path string `json:"path,omitempty"`
+}
+
+//UploadFileRes 上传文件返回结果
+type UploadFileRes struct {
+	util.CommonError
+	URL           string `json:"url"`           //上传url
+	Token         string `json:"token"`         //token
+	Authorization string `json:"authorization"` //authorization
+	FileID        string `json:"file_id"`       //文件ID
+	CosFileID     string `json:"cos_file_id"`   //cos文件ID
+}
+
+//BatchDownloadFileReq 上传文件请求值
+type BatchDownloadFileReq struct {
+	Env      string          `json:"env,omitempty"`
+	FileList []*DownloadFile `json:"file_list,omitempty"`
+}
+
+//DownloadFile 文件信息
+type DownloadFile struct {
+	FileID string `json:"fileid"`  //文件ID
+	MaxAge int64  `json:"max_age"` //下载链接有效期
+}
+
+//BatchDownloadFileRes 上传文件返回结果
+type BatchDownloadFileRes struct {
+	util.CommonError
+	FileList []struct {
+		FileID      string `json:"file_id"`      //文件ID
+		DownloadURL string `json:"download_url"` //下载链接
+		Status      int64  `json:"status"`       //状态码
+		ErrMsg      string `json:"errmsg"`       //该文件错误信息
+	} `json:"file_list"`
+}
+
+//BatchDeleteFileReq 批量删除文件请求参数
+type BatchDeleteFileReq struct {
+	Env        string   `json:"env,omitempty"`
+	FileIDList []string `json:"fileid_list,omitempty"`
+}
+
+//BatchDeleteFileRes 批量删除文件返回结果
+type BatchDeleteFileRes struct {
+	util.CommonError
+	DeleteList []struct {
+		FileID string `json:"fileid"`
+		Status int64  `json:"status"`
+		ErrMsg string `json:"errmsg"`
+	} `json:"delete_list"`
+}
+
+//UploadFile 上传文件
+//reference:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/storage/uploadFile.html
+func (tcb *Tcb) UploadFile(env, path string) (*UploadFileRes, error) {
+	accessToken, err := tcb.GetAccessToken()
+	if err != nil {
+		return nil, err
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", uploadFilePathURL, accessToken)
+	req := &UploadFileReq{
+		Env:  env,
+		Path: path,
+	}
+	response, err := util.PostJSON(uri, req)
+	if err != nil {
+		return nil, err
+	}
+	uploadFileRes := &UploadFileRes{}
+	err = util.DecodeWithError(response, uploadFileRes, "UploadFile")
+	return uploadFileRes, err
+}
+
+//BatchDownloadFile 获取文件下载链接
+//reference:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/storage/batchDownloadFile.html
+func (tcb *Tcb) BatchDownloadFile(env string, fileList []*DownloadFile) (*BatchDownloadFileRes, error) {
+	accessToken, err := tcb.GetAccessToken()
+	if err != nil {
+		return nil, err
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", batchDownloadFileURL, accessToken)
+	req := &BatchDownloadFileReq{
+		Env:      env,
+		FileList: fileList,
+	}
+	response, err := util.PostJSON(uri, req)
+	if err != nil {
+		return nil, err
+	}
+	batchDownloadFileRes := &BatchDownloadFileRes{}
+	err = util.DecodeWithError(response, batchDownloadFileRes, "BatchDownloadFile")
+	return batchDownloadFileRes, err
+}
+
+//BatchDeleteFile 批量删除文件
+//reference:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/storage/batchDeleteFile.html
+func (tcb *Tcb) BatchDeleteFile(env string, fileIDList []string) (*BatchDeleteFileRes, error) {
+	accessToken, err := tcb.GetAccessToken()
+	if err != nil {
+		return nil, err
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", batchDeleteFileURL, accessToken)
+	req := &BatchDeleteFileReq{
+		Env:        env,
+		FileIDList: fileIDList,
+	}
+	response, err := util.PostJSON(uri, req)
+	if err != nil {
+		return nil, err
+	}
+	batchDeleteFileRes := &BatchDeleteFileRes{}
+	err = util.DecodeWithError(response, batchDeleteFileRes, "BatchDeleteFile")
+	return batchDeleteFileRes, nil
+}

+ 16 - 0
tcb/tcb.go

@@ -0,0 +1,16 @@
+package tcb
+
+import "github.com/silenceper/wechat/context"
+
+//Tcb Tencent Cloud Base
+type Tcb struct{
+	*context.Context
+}
+
+//NewTcb new Tencent Cloud Base
+func NewTcb(context *context.Context)*Tcb{
+	return &Tcb{
+		context,
+	}
+}
+

+ 26 - 0
util/error.go

@@ -3,6 +3,7 @@ package util
 import (
 	"encoding/json"
 	"fmt"
+	"reflect"
 )
 
 // CommonError 微信返回的通用错误json
@@ -23,3 +24,28 @@ func DecodeWithCommonError(response []byte, apiName string) (err error) {
 	}
 	return nil
 }
+
+// DecodeWithError 将返回值按照解析
+func DecodeWithError(response []byte, obj interface{}, apiName string) error {
+	err := json.Unmarshal(response, obj)
+	if err != nil {
+		return fmt.Errorf("json Unmarshal Error, err=%v", err)
+	}
+	responseObj := reflect.ValueOf(obj)
+	if !responseObj.IsValid() {
+		return fmt.Errorf("obj is invalid")
+	}
+	commonError := responseObj.Elem().FieldByName("CommonError")
+	if !commonError.IsValid() || commonError.Kind() != reflect.Struct {
+		return fmt.Errorf("commonError is invalid or not struct")
+	}
+	errCode := commonError.FieldByName("ErrCode")
+	errMsg := commonError.FieldByName("ErrMsg")
+	if !errCode.IsValid() || !errMsg.IsValid() {
+		return fmt.Errorf("errcode or errmsg is invalid")
+	}
+	if errCode.Int() != 0 {
+		return fmt.Errorf("%s Error , errcode=%d , errmsg=%s", apiName, errCode.Int(), errMsg.String())
+	}
+	return nil
+}

+ 17 - 3
util/http.go

@@ -7,13 +7,14 @@ import (
 	"encoding/pem"
 	"encoding/xml"
 	"fmt"
-	"golang.org/x/crypto/pkcs12"
 	"io"
 	"io/ioutil"
 	"log"
 	"mime/multipart"
 	"net/http"
 	"os"
+
+	"golang.org/x/crypto/pkcs12"
 )
 
 //HTTPGet get 请求
@@ -30,17 +31,30 @@ func HTTPGet(uri string) ([]byte, error) {
 	return ioutil.ReadAll(response.Body)
 }
 
+//HTTPPost post 请求
+func HTTPPost(uri string, data string) ([]byte, error) {
+	body := bytes.NewBuffer([]byte(data))
+	response, err := http.Post(uri, "", body)
+	if err != nil {
+		return nil, err
+	}
+
+	defer response.Body.Close()
+	if response.StatusCode != http.StatusOK {
+		return nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode)
+	}
+	return ioutil.ReadAll(response.Body)
+}
+
 //PostJSON post json 数据请求
 func PostJSON(uri string, obj interface{}) ([]byte, error) {
 	jsonData, err := json.Marshal(obj)
 	if err != nil {
 		return nil, err
 	}
-
 	jsonData = bytes.Replace(jsonData, []byte("\\u003c"), []byte("<"), -1)
 	jsonData = bytes.Replace(jsonData, []byte("\\u003e"), []byte(">"), -1)
 	jsonData = bytes.Replace(jsonData, []byte("\\u0026"), []byte("&"), -1)
-
 	body := bytes.NewBuffer(jsonData)
 	response, err := http.Post(uri, "application/json;charset=utf-8", body)
 	if err != nil {

+ 0 - 144
vendor/vendor.json

@@ -1,144 +0,0 @@
-{
-	"comment": "",
-	"ignore": "test",
-	"package": [
-		{
-			"checksumSHA1": "ZZ4FL7s5f8QK4RysjZObSBYGOLY=",
-			"path": "github.com/astaxie/beego",
-			"revision": "2d87d4feafeea0a133d217a82e6e02df0348fed5",
-			"revisionTime": "2016-09-22T15:18:45Z"
-		},
-		{
-			"checksumSHA1": "LwEiQ/Hyb7Ul28TSlwowN9cpWDY=",
-			"path": "github.com/astaxie/beego/config",
-			"revision": "2d87d4feafeea0a133d217a82e6e02df0348fed5",
-			"revisionTime": "2016-09-22T15:18:45Z"
-		},
-		{
-			"checksumSHA1": "s+gj1rES9SvvCIyF8W2tzlziSPE=",
-			"path": "github.com/astaxie/beego/context",
-			"revision": "2d87d4feafeea0a133d217a82e6e02df0348fed5",
-			"revisionTime": "2016-09-22T15:18:45Z"
-		},
-		{
-			"checksumSHA1": "PDNn16w89zWODshT9zlPzSmWZFA=",
-			"path": "github.com/astaxie/beego/grace",
-			"revision": "2d87d4feafeea0a133d217a82e6e02df0348fed5",
-			"revisionTime": "2016-09-22T15:18:45Z"
-		},
-		{
-			"checksumSHA1": "Iz/p1UTvFNe5HFeohX7cvKEOQW0=",
-			"path": "github.com/astaxie/beego/logs",
-			"revision": "2d87d4feafeea0a133d217a82e6e02df0348fed5",
-			"revisionTime": "2016-09-22T15:18:45Z"
-		},
-		{
-			"checksumSHA1": "R797q1pCbp086SraUETxX1rsJYw=",
-			"path": "github.com/astaxie/beego/session",
-			"revision": "2d87d4feafeea0a133d217a82e6e02df0348fed5",
-			"revisionTime": "2016-09-22T15:18:45Z"
-		},
-		{
-			"checksumSHA1": "rxxln8GHFToVnaEJz4JMv0WbaKc=",
-			"path": "github.com/astaxie/beego/toolbox",
-			"revision": "2d87d4feafeea0a133d217a82e6e02df0348fed5",
-			"revisionTime": "2016-09-22T15:18:45Z"
-		},
-		{
-			"checksumSHA1": "fRJk3RZPPz6ovbautfsfxAk+CrI=",
-			"path": "github.com/astaxie/beego/utils",
-			"revision": "2d87d4feafeea0a133d217a82e6e02df0348fed5",
-			"revisionTime": "2016-09-22T15:18:45Z"
-		},
-		{
-			"checksumSHA1": "fNAC4qgZDqF3kxq74/yyk3PWdy8=",
-			"path": "github.com/bradfitz/gomemcache/memcache",
-			"revision": "fb1f79c6b65acda83063cbc69f6bba1522558bfc",
-			"revisionTime": "2016-01-17T19:21:50Z"
-		},
-		{
-			"checksumSHA1": "RsNwOto8G8aXIiRrlFn4dtU9q/g=",
-			"path": "github.com/gin-gonic/gin",
-			"revision": "e2212d40c62a98b388a5eb48ecbdcf88534688ba",
-			"revisionTime": "2016-12-04T22:13:08Z"
-		},
-		{
-			"checksumSHA1": "UsILDoIB2S7ra+w2fMdb85mX3HM=",
-			"path": "github.com/gin-gonic/gin/binding",
-			"revision": "e2212d40c62a98b388a5eb48ecbdcf88534688ba",
-			"revisionTime": "2016-12-04T22:13:08Z"
-		},
-		{
-			"checksumSHA1": "PHv9FNb7YavJWtAHcY6ZgXmkmHs=",
-			"path": "github.com/gin-gonic/gin/render",
-			"revision": "e2212d40c62a98b388a5eb48ecbdcf88534688ba",
-			"revisionTime": "2016-12-04T22:13:08Z"
-		},
-		{
-			"checksumSHA1": "kBeNcaKk56FguvPSUCEaH6AxpRc=",
-			"path": "github.com/golang/protobuf/proto",
-			"revision": "8ee79997227bf9b34611aee7946ae64735e6fd93",
-			"revisionTime": "2016-11-17T03:31:26Z"
-		},
-		{
-			"checksumSHA1": "w3QCCIYHgZzIXQ+xTl7oLfFrXHs=",
-			"path": "github.com/gomodule/redigo/internal",
-			"revision": "2cd21d9966bf7ff9ae091419744f0b3fb0fecace",
-			"revisionTime": "2018-06-27T14:45:07Z"
-		},
-		{
-			"checksumSHA1": "To/N5YA/FD0Rrs6r2OOmHXgxYwI=",
-			"path": "github.com/gomodule/redigo/redis",
-			"revision": "2cd21d9966bf7ff9ae091419744f0b3fb0fecace",
-			"revisionTime": "2018-06-27T14:45:07Z"
-		},
-		{
-			"checksumSHA1": "b0T0Hzd+zYk+OCDTFMps+jwa/nY=",
-			"path": "github.com/manucorporat/sse",
-			"revision": "ee05b128a739a0fb76c7ebd3ae4810c1de808d6d",
-			"revisionTime": "2016-01-26T18:01:36Z"
-		},
-		{
-			"checksumSHA1": "xZuhljnmBysJPta/lMyYmJdujCg=",
-			"path": "github.com/mattn/go-isatty",
-			"revision": "30a891c33c7cde7b02a981314b4228ec99380cca",
-			"revisionTime": "2016-11-23T14:36:37Z"
-		},
-		{
-			"checksumSHA1": "PJY7uCr3UnX4/Mf/RoWnbieSZ8o=",
-			"path": "golang.org/x/crypto/pkcs12",
-			"revision": "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94",
-			"revisionTime": "2017-09-21T17:41:56Z"
-		},
-		{
-			"checksumSHA1": "iVJcif9M9uefvvqHCNR9VQrjc/s=",
-			"path": "golang.org/x/crypto/pkcs12/internal/rc2",
-			"revision": "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94",
-			"revisionTime": "2017-09-21T17:41:56Z"
-		},
-		{
-			"checksumSHA1": "pancewZW3HwGvpDwfH5Imrbadc4=",
-			"path": "golang.org/x/net/context",
-			"revision": ""
-		},
-		{
-			"checksumSHA1": "8fD/im5Kwvy3JgmxulDTambmE8w=",
-			"path": "golang.org/x/sys/unix",
-			"revision": "a646d33e2ee3172a661fc09bca23bb4889a41bc8",
-			"revisionTime": "2016-07-15T05:43:45Z"
-		},
-		{
-			"checksumSHA1": "39V1idWER42Lmcmg2Uy40wMzOlo=",
-			"path": "gopkg.in/go-playground/validator.v8",
-			"revision": "5f57d2222ad794d0dffb07e664ea05e2ee07d60c",
-			"revisionTime": "2016-07-18T13:41:25Z"
-		},
-		{
-			"checksumSHA1": "12GqsW8PiRPnezDDy0v4brZrndM=",
-			"path": "gopkg.in/yaml.v2",
-			"revision": "a5b47d31c556af34a302ce5d659e6fea44d90de0",
-			"revisionTime": "2016-09-28T15:37:09Z"
-		}
-	],
-	"rootPath": "github.com/silenceper/wechat"
-}

+ 10 - 4
wechat.go

@@ -1,21 +1,22 @@
 package wechat
 
 import (
-	"github.com/silenceper/wechat/device"
 	"net/http"
 	"sync"
 
 	"github.com/silenceper/wechat/cache"
 	"github.com/silenceper/wechat/context"
+	"github.com/silenceper/wechat/device"
 	"github.com/silenceper/wechat/js"
 	"github.com/silenceper/wechat/material"
 	"github.com/silenceper/wechat/menu"
+	"github.com/silenceper/wechat/message"
 	"github.com/silenceper/wechat/miniprogram"
 	"github.com/silenceper/wechat/oauth"
 	"github.com/silenceper/wechat/pay"
 	"github.com/silenceper/wechat/qr"
 	"github.com/silenceper/wechat/server"
-	"github.com/silenceper/wechat/template"
+	"github.com/silenceper/wechat/tcb"
 	"github.com/silenceper/wechat/user"
 )
 
@@ -94,8 +95,8 @@ func (wc *Wechat) GetUser() *user.User {
 }
 
 // GetTemplate 模板消息接口
-func (wc *Wechat) GetTemplate() *template.Template {
-	return template.NewTemplate(wc.Context)
+func (wc *Wechat) GetTemplate() *message.Template {
+	return message.NewTemplate(wc.Context)
 }
 
 // GetPay 返回支付消息的实例
@@ -117,3 +118,8 @@ func (wc *Wechat) GetMiniProgram() *miniprogram.MiniProgram {
 func (wc *Wechat) GetDevice() *device.Device {
 	return device.NewDevice(wc.Context)
 }
+
+// GetTcb 获取小程序-云开发的实例
+func (wc *Wechat) GetTcb() *tcb.Tcb {
+	return tcb.NewTcb(wc.Context)
+}