Эх сурвалжийг харах

weixinutil添加rpc调用

wanghuidong 5 жил өмнө
parent
commit
a014f32b65
11 өөрчлөгдсөн 334 нэмэгдсэн , 920 устгасан
  1. 1 380
      README.md
  2. 1 1
      go.mod
  3. 2 0
      go.sum
  4. 35 0
      main.go
  5. 120 0
      rpc/rpc.go
  6. 79 0
      weixin/config.go
  7. 58 0
      weixin/httpHandle.go
  8. 1 0
      weixin/muxEventHandle.go
  9. 25 1
      weixin/weixin.go
  10. 11 0
      wxCfg.json
  11. 1 538
      接口说明.md

+ 1 - 380
README.md

@@ -1,386 +1,7 @@
-# 微信公众平台 – Go语言版本
+# 微信公众平台通用服务 – Go语言版本
 
 ## 简介
 
-这是一个使用Go语言对微信公众平台的封装。参考了微信公众平台API文档
-
 ## 入门
 
 ### 安装
-
-通过执行下列语句就可以完成安装
-
-	go get app.yhyue.com/BP/wx_util
-
-### 注册微信公众平台
-
-注册微信公众平台,填写验证微信公众平台的Token
-
-### 示例
-
-```Go
-package main
-
-import (
-	"github.com/wizjin/weixin"
-	"net/http"
-)
-
-// 文本消息的处理函数
-func Echo(w weixin.ResponseWriter, r *weixin.Request) {
-	txt := r.Content			// 获取用户发送的消息
-	w.ReplyText(txt)			// 回复一条文本消息
-	w.PostText("Post:" + txt)	// 发送一条文本消息
-}
-
-// 关注事件的处理函数
-func Subscribe(w weixin.ResponseWriter, r *weixin.Request) {
-	w.ReplyText("欢迎关注") // 有新人关注,返回欢迎消息
-}
-
-func main() {
-	// my-token 验证微信公众平台的Token
-	// app-id, app-secret用于高级API调用。
-	// 如果仅使用接收/回复消息,则可以不填写,使用下面语句
-	// mux := weixin.New("my-token", "", "")
-	mux := weixin.New("my-token", "app-id", "app-secret")
-	// 设置AES密钥,如果不使用AES可以省略这行代码
-	mux.SetEncodingAESKey("encoding-AES-key")
-	// 注册文本消息的处理函数
-	mux.HandleFunc(weixin.MsgTypeText, Echo)
-	// 注册关注事件的处理函数
-	mux.HandleFunc(weixin.MsgTypeEventSubscribe, Subscribe)
-	http.Handle("/", mux) // 注册接收微信服务器数据的接口URI
-	http.ListenAndServe(":80", nil) // 启动接收微信数据服务器
-}
-```
-
-微信公众平台要求在收到消息后5秒内回复消息(Reply接口)
-如果时间操作很长,则可以使用Post接口发送消息
-如果只使用Post接口发送消息,则需要先调用ReplyOK来告知微信不用等待回复。
-
-### 处理函数
-
-处理函数的定义可以使用下面的形式
-
-```Go
-func Func(w weixin.ResponseWriter, r *weixin.Request) {
-	...
-}
-```
-
-可以注册的处理函数类型有以下几种
-
-- `weixin.MsgTypeText`				接收文本消息
-- `weixin.MsgTypeImage`				接收图片消息
-- `weixin.MsgTypeVoice`				接收语音消息
-- `weixin.MsgTypeVideo`				接收视频消息
-- `weixin.MsgTypeShortVideo`		接收小视频消息
-- `weixin.MsgTypeLocation`			接收地理位置消息
-- `weixin.MsgTypeLink`				接收链接消息
-- `weixin.MsgTypeEventSubscribe`	接收关注事件
-- `weixin.MsgTypeEventUnsubscribe`	接收取消关注事件
-- `weixin.MsgTypeEventScan`			接收扫描二维码事件
-- `weixin.MsgTypeEventView`			接收点击菜单跳转链接时的事件
-- `weixin.MsgTypeEventClick`		接收自定义菜单事件
-- `weixin.MsgTypeEventLocation`		接收上报地理位置事件
-- `weixin.MsgTypeEventTemplateSent` 接收模版消息发送结果
-
-### 发送被动响应消息
-
-需要发送被动响应消息,可通过`weixin.ResponseWriter`的下列方法完成
-
-- `ReplyOK()`								无同步消息回复
-- `ReplyText(text)`							回复文本消息
-- `ReplyImage(mediaId)`						回复图片消息
-- `ReplyVoice(mediaId)`						回复语音消息
-- `ReplyVideo(mediaId, title, description)`	回复视频消息
-- `ReplyMusic(music)`						回复音乐消息
-- `ReplyNews(articles)`						回复图文消息
-
-### 发送客服消息
-
-- `PostText(text)`							发送文本消息
-- `PostImage(mediaId)`						发送图片消息
-- `PostVoice(mediaId)`						发送语音消息
-- `PostVideo(mediaId, title, description)`	发送视频消息
-- `PostMusic(music)`						发送音乐消息
-- `PostNews(articles)`						发送图文消息
-
-### 发送模版消息
-
-如需要发送模版消息,需要先获取模版ID,之后再根据ID发送。
-
-```Go
-func GetTemplateId(wx *weixin.Weixin) {
-	tid, err := wx.AddTemplate("TM00015")
-	if err != nil {
-		fmt.Println(err)
-	} else {
-		fmt.Println(ips)	// 模版ID
-	}
-}
-```
-
-随后可以发送模版消息了。
-
-```Go
-func SendTemplateMessage(w weixin.ResponseWriter, r *weixin.Request) {
-	templateId := ...
-	url := ...
-	msgid, err := w.PostTemplateMessage(templateId, url,
-		weixin.TmplData{ "first": weixin.TmplItem{"Hello World!", "#173177"}})
-	if err != nil {
-		fmt.Println(err)
-	} else {
-		fmt.Println(msgid)
-	}
-}
-```
-
-在模版消息发送成功后,还会通过类型为`MsgTypeEventTemplateSent`的消息推送获得发送结果。
-
-- `TemplateSentStatusSuccess` 		发送成功
-- `TemplateSentStatusUserBlock`		发送失败,用户拒绝接收
-- `TemplateSentStatusSystemFailed`	发送失败,系统原因
-
-
-### 上传/下载多媒体文件
-
-使用如下函数可以用来上传多媒体文件:
-
-`UploadMediaFromFile(mediaType string, filepath string)`
-
-示例 (用一张本地图片来返回图片消息):
-
-```Go
-func ReciveMessage(w weixin.ResponseWriter, r *weixin.Request) {
-	// 上传本地文件并获取MediaID
-	mediaId, err := w.UploadMediaFromFile(weixin.MediaTypeImage, "/my-file-path")
-	if err != nil {
-		w.ReplyText("上传图片失败")
-	} else {
-		w.ReplyImage(mediaId)	// 利用获取的MediaId来返回图片消息
-	}
-}
-```
-
-使用如下函数可以用来下载多媒体文件:
-
-`DownloadMediaToFile(mediaId string, filepath string)`
-
-示例 (收到一条图片消息,然后保存图片到本地文件):
-
-```Go
-func ReciveImageMessage(w weixin.ResponseWriter, r *weixin.Request) {
-	// 下载文件并保存到本地
-	err := w.DownloadMediaToFile(r.MediaId, "/my-file-path")
-	if err != nil {
-		w.ReplyText("保存图片失败")
-	} else {
-		w.ReplyText("保存图片成功")
-	}
-}
-```
-
-### 获取微信服务器IP地址
-
-示例,获取微信服务器IP地址列表
-
-```Go
-func GetIpList(wx *weixin.Weixin) {
-	ips, err := wx.GetIpList()
-	if err != nil {
-		fmt.Println(err)
-	} else {
-		fmt.Println(ips)	// Ip地址列表
-	}
-}
-```
-
-### 获取AccessToken
-
-示例,获取AccessToken
-
-```Go
-func GetAccessToken(wx *weixin.Weixin) {
-	a := wx.GetAccessToken
-	if time.Until(token.Expires).Seconds() > 0 {
-		fmt.Println(a.Token)	// AccessToken
-	} else {
-		fmt.Println("Timeout")	// 超时
-	}
-}
-```
-
-### 创建/换取二维码
-
-示例,创建临时二维码
-
-```Go
-func CreateQRScene(wx *weixin.Weixin) {
-	// 二维码ID - 1000
-	// 过期时间 - 1800秒
-	qr, err := wx.CreateQRScene(1000, 1800)
-	if err != nil {
-		fmt.Println(err)
-	} else {
-		url := qr.ToURL() // 获取二维码的URL
-		fmt.Println(url)
-	}
-}
-```
-
-示例,创建永久二维码
-
-```Go
-func CreateQRScene(wx *weixin.Weixin) {
-	// 二维码ID - 1001
-	qr, err := wx.CreateQRLimitScene(1001)
-	if err != nil {
-		fmt.Println(err)
-	} else {
-		url := qr.ToURL() // 获取二维码的URL
-		fmt.Println(url)
-	}
-}
-```
-
-### 长链接转短链接接口
-
-```Go
-func ShortURL(wx *weixin.Weixin) {
-	// 长链接转短链接
-	url, err := wx.ShortURL("http://mp.weixin.qq.com/wiki/10/165c9b15eddcfbd8699ac12b0bd89ae6.html")
-	if err != nil {
-		fmt.Println(err)
-	} else {
-		fmt.Println(url)
-	}
-}
-```
-
-### 自定义菜单
-
-示例,创建自定义菜单
-
-```Go
-func CreateMenu(wx *weixin.Weixin) {
-	menu := &weixin.Menu{make([]weixin.MenuButton, 2)}
-	menu.Buttons[0].Name = "我的菜单"
-	menu.Buttons[0].Type = weixin.MenuButtonTypeUrl
-	menu.Buttons[0].Url = "https://mp.weixin.qq.com"
-	menu.Buttons[1].Name = "我的子菜单"
-	menu.Buttons[1].SubButtons = make([]weixin.MenuButton, 1)
-	menu.Buttons[1].SubButtons[0].Name = "测试"
-	menu.Buttons[1].SubButtons[0].Type = weixin.MenuButtonTypeKey
-	menu.Buttons[1].SubButtons[0].Key = "MyKey001"
-	err := wx.CreateMenu(menu)
-	if err != nil {
-		fmt.Println(err)
-	}
-}
-```
-
-自定义菜单的类型有如下几种
-
-- `weixin.MenuButtonTypeKey`				点击推事件
-- `weixin.MenuButtonTypeUrl`				跳转URL
-- `weixin.MenuButtonTypeScancodePush`		扫码推事件
-- `weixin.MenuButtonTypeScancodeWaitmsg`	扫码推事件且弹出“消息接收中”提示框
-- `weixin.MenuButtonTypePicSysphoto`		弹出系统拍照发图
-- `weixin.MenuButtonTypePicPhotoOrAlbum`	弹出拍照或者相册发图
-- `weixin.MenuButtonTypePicWeixin`			弹出微信相册发图器
-- `weixin.MenuButtonTypeLocationSelect`		弹出地理位置选择器
-- `weixin.MenuButtonTypeMediaId`			下发消息(除文本消息)
-- `weixin.MenuButtonTypeViewLimited`		跳转图文消息URL
-
-示例,获取自定义菜单
-
-```Go
-func DeleteMenu(wx *weixin.Weixin) {
-	menu, err := wx.GetMenu()
-	if err != nil {
-		fmt.Println(err)
-	} else {
-		fmt.Println(menu)
-	}
-}
-```
-
-示例,删除自定义菜单
-
-```Go
-func DeleteMenu(wx *weixin.Weixin) {
-	err := wx.DeleteMenu()
-	if err != nil {
-		fmt.Println(err)
-	}
-}
-```
-
-### JSSDK签名
-
-示例,生成JSSDK签名
-```Go
-func SignJSSDK(wx *weixin.Weixin, url string) {
-	timestamp := time.Now().Unix()
-	noncestr := fmt.Sprintf("%d", c.randreader.Int())
-	sign, err := wx.JsSignature(url, timestamp, noncestr)
-	if err != nil {
-		fmt.Println(err)
-	} else {
-		fmt.Println(sign)
-	}
-}
-```
-
-### 重定向URL生成
-
-示例,生成重定向URL
-```Go
-func CreateRedirect(wx *weixin.Weixin, url string) {
-	redirect := wx.CreateRedirectURL(url, weixin.RedirectURLScopeBasic, "")
-}
-```
-
-URL的类型有如下几种:
-
-- `RedirectURLScopeBasic`					基本授权,仅用来获取OpenId或UnionId
-- `RedirectURLScopeUserInfo`				高级授权,可以用于获取用户基本信息,需要用户同意
-
-### 用户OpenId和UnionId获取
-
-示例,获取用户OpenId和UnionId
-```Go
-func GetUserId(wx *weixin.Weixin, code string) {
-	user, err := wx.GetUserAccessToken(code)
-	if err != nil {
-		fmt.Println(err)
-	} else {
-		fmt.Println(user.OpenId)
-		fmt.Println(user.UnionId)
-	}
-}
-```
-
-### 用户信息获取
-
-示例,获取用户信息
-```Go
-func GetUserInfo(wx *weixin.Weixin, openid string) {
-	user, err := wx.GetUserInfo(openid)
-	if err != nil {
-		fmt.Println(err)
-	} else {
-		fmt.Println(user.Nickname)
-		fmt.Println(user.Sex)
-		fmt.Println(user.City)
-		fmt.Println(user.Country)
-		fmt.Println(user.Province)
-		fmt.Println(user.HeadImageUrl)
-		fmt.Println(user.SubscribeTime)
-		fmt.Println(user.Remark)
-	}
-}
-```

+ 1 - 1
go.mod

@@ -1,3 +1,3 @@
 module app.yhyue.com/BP/weixin_util
 
-go 1.14
+require app.yhyue.com/BP/common_utils v0.0.0-20200528093434-335d3b55fd71

+ 2 - 0
go.sum

@@ -0,0 +1,2 @@
+app.yhyue.com/BP/common_utils v0.0.0-20200528093434-335d3b55fd71 h1:G5HkQDkgZi06pnxU9av87QmtXIcbhCNlBjgLVHMjW/A=
+app.yhyue.com/BP/common_utils v0.0.0-20200528093434-335d3b55fd71/go.mod h1:+/bZVhDe9fYucfSMOIUgPMymR9M/M8+4t0GIHnaftho=

+ 35 - 0
main.go

@@ -0,0 +1,35 @@
+package main
+
+import (
+	"app.yhyue.com/BP/weixin_util/rpc"
+	"app.yhyue.com/BP/weixin_util/weixin"
+	"log"
+	"net/http"
+)
+
+func init() {
+	weixin.ReadConfig("./wxCfg.json", &weixin.WxConfig)
+	log.Println("WxConfig:", weixin.WxConfig)
+	//weixin.SE = encrypt.SimpleEncrypt{Key: weixin.WxConfig.EncryptKey}
+}
+
+func main() {
+	// my-token 验证微信公众平台的Token
+	// app-id, app-secret用于高级API调用。
+	// 如果仅使用接收/回复消息,则可以不填写,使用下面语句
+	// mux := New("my-token", "", "")
+	weixin.Mux = weixin.New(weixin.WxConfig.Token, weixin.WxConfig.AppId, weixin.WxConfig.Secret)
+	/*mux.HandleFunc(weixin.MsgTypeText,MsgTextHandler)
+	mux.HandleFunc(MsgTypeImage, ImageMsgHandler)
+	mux.HandleFunc(MsgTypeEventSubscribe, SubscribeHandler)
+	mux.HandleFunc(MsgTypeEventUnsubscribe, UnSubscribeHandler)
+	mux.HandleFunc(MsgTypeEventClick, ClickHandler)
+	mux.HandleFunc(MsgTypeVoice, MsgVoiceHandler)
+	mux.HandleFunc("SCAN", ScanHandler)*/
+	http.Handle(weixin.WxConfig.Prefix, weixin.Mux) // 注册接收微信服务器数据的接口URI
+	//追加的http事件
+	//http.HandleFunc("/wx/createMenu", weixin.CreateMenu)
+	http.HandleFunc("/wx/sso", weixin.Sso)
+	rpc.StartWeiXinRpc(weixin.Mux)
+	http.ListenAndServe(":80", nil)
+}

+ 120 - 0
rpc/rpc.go

@@ -0,0 +1,120 @@
+package rpc
+
+import (
+	"app.yhyue.com/BP/weixin_util/weixin"
+	"log"
+	"net"
+	"net/http"
+	"net/rpc"
+)
+
+const (
+	tplapi_url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token="
+	itmapi_url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token="
+)
+
+//提供远程调用功能,该接口可以提供多个服务函数
+type WeiXinRpc struct {
+	wx *weixin.Weixin
+}
+type NotifyMsg struct {
+	Openid      string //用户id
+	Remark      string //消息内容,其他属性待加
+	KeyWord1    string
+	KeyWord2    string
+	Title       string
+	Url         string
+	Date        string
+	KeyWord3    string
+	Color       string
+	DetailColor string
+	TplId       string
+}
+
+//RPCResult
+type Result string
+
+func (wxRPC *WeiXinRpc) SendTplMsg(msgTmpIdKey string, param *NotifyMsg, ret *Result) (err error) {
+	var msg struct {
+		ToUser   string `json:"touser"`
+		TplId    string `json:"template_id"`
+		Url      string `json:"url"`
+		Topcolor string `json:"topcolor"`
+		Data     struct {
+			Title struct {
+				Value string `json:"value"`
+			} `json:"first"`
+			KeyWord1 struct {
+				Value string `json:"value"`
+			} `json:"keyword1"`
+			KeyWord2 struct {
+				Value string `json:"value"`
+			} `json:"keyword2"`
+			KeyWord3 struct {
+				Value string `json:"value"`
+			} `json:"keyword3"`
+			Remark struct {
+				Value string `json:"value"`
+			} `json:"remark"`
+		} `json:"data"`
+	}
+	msg.ToUser = param.Openid
+	msg.TplId = weixin.WxConfig.MessageTpl["msgTmpIdKey"]
+	msg.Url = param.Url
+	msg.Data.Title.Value = param.Title
+	msg.Data.KeyWord1.Value = param.KeyWord1
+	msg.Data.KeyWord2.Value = param.KeyWord2
+	msg.Data.KeyWord3.Value = param.KeyWord3
+	msg.Data.Remark.Value = param.Remark
+	log.Printf("msgTmpIdKey:[%s] send TplMsg:[%v]", msgTmpIdKey, msg)
+	err = wxRPC.wx.PostTextCustom(tplapi_url, &msg)
+	if err != nil {
+		log.Printf("msgTmpIdKey:[%s] send TplMsg[%s]", msgTmpIdKey, err.Error())
+	} else {
+		log.Printf("msgTmpIdKey:[%s] send  success!", msgTmpIdKey)
+	}
+	return
+}
+
+func (wxRPC *WeiXinRpc) CreateMenu(menu *weixin.Menu) {
+	/*url1 := "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + weixin.WxConfig.AppId + "&redirect_uri=http%3A%2F%2F" + weixin.WxConfig.Domain + "%2Fwx%2Fsso%3Fcallback%3Dhttp%3A%2F%2F" + weixin.WxConfig.Domain + "%2Fkjj%2Fbusiness1&response_type=code&scope=snsapi_base&state=1#wechat_redirect"
+	menu := &Menu{make([]MenuButton, 2)}
+	menu.Buttons[0].Name = "业务1"
+	menu.Buttons[0].Type = MenuButtonTypeUrl
+	menu.Buttons[0].Url = url1
+
+	url2 := "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + weixin.WxConfig.AppId + "&redirect_uri=http%3A%2F%2F" + weixin.WxConfig.Domain + "%2Fwx%2Fsso%3Fcallback%3Dhttp%3A%2F%2F" + weixin.WxConfig.Domain + "%2Fkjj%2Fbusiness2&response_type=code&scope=snsapi_base&state=1#wechat_redirect"
+	menu.Buttons[1].Name = "业务2"
+	menu.Buttons[1].Type = MenuButtonTypeUrl
+	menu.Buttons[1].Url = url2*/
+
+	err := weixin.Mux.CreateMenu(menu)
+	if err != nil {
+		log.Println("菜单创建失败")
+	} else {
+		log.Println("菜单创建成功!")
+	}
+}
+
+var listen net.Listener
+
+func StartWeiXinRpc(wx *weixin.Weixin) {
+	wrpc := &WeiXinRpc{wx: wx}
+	//在此可以注册多个Rpc服务接口
+	rpc.Register(wrpc)
+	rpc.HandleHTTP()
+	var err error
+	listen, err = net.Listen("tcp", ":"+weixin.WxConfig.RpcPort)
+	if err != nil {
+		log.Println(err.Error())
+	} else {
+		go http.Serve(listen, nil)
+	}
+}
+
+func DestoryRPC() {
+	if listen != nil {
+		log.Println("close weixin rpc netlisten")
+		listen.Close()
+	}
+}

+ 79 - 0
weixin/config.go

@@ -0,0 +1,79 @@
+package weixin
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"os"
+)
+
+type wxCfg struct {
+	AppId      string            `json:"app_id"` //微信公众号app_id
+	Secret     string            `json:"secret"` //秘钥
+	Prefix     string            `json:"prefix"` //微信消息通讯前缀
+	Token      string            `json:"token"`
+	Domain     string            `json:"domain"`      //微信应用域名
+	MessageTpl map[string]string `json:"message_tpl"` //消息模板配置
+	RpcPort    string            `json:"rpc_port"`    //RPC端口
+}
+
+const (
+	WxOpenAuthUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code"
+	ErrorTemplate = `
+<html>
+<head>
+<title>企明星-企业服务网-全国最大最真实的企业社区</title>
+<meta name="msvalidate.01" content="D5F3ADC7EB4E65FFB8BF943AD56DD1F7" />
+<meta charset="utf-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge,Chrome=1" />
+<meta name="viewport" content="width=device-width,initial-scale=1.0">
+<meta name="renderer" content="webkit">
+<meta name="Keywords" content="企明星,企业服务网,企业社区,企业服务,微官网"/>
+<meta name="Description" content="企明星是一款面向企业和个人提供服务的互联网产品,提供企业社区(企业产品服务信息发布、企业信息查询、企业关系网)、微官网、企业关系网、政策法规查询。"/>
+</head>
+<body>
+授权验证失败,请微信关注”企明星“,点击菜单进入。
+</body>
+</html>
+	`
+)
+
+func ReadConfig(config ...interface{}) {
+	var r *os.File
+	if len(config) > 1 {
+		filepath, _ := config[0].(string)
+		r, _ = os.Open(filepath)
+		defer r.Close()
+		bs, _ := ioutil.ReadAll(r)
+		json.Unmarshal(bs, config[1])
+	} else {
+		r, _ = os.Open("./config.json")
+		defer r.Close()
+		bs, _ := ioutil.ReadAll(r)
+		json.Unmarshal(bs, config[0])
+	}
+}
+
+//程序修改配置表后,调用写入配置文件
+func WriteSysConfig(config ...interface{}) {
+	var r *os.File
+	var configObj interface{}
+	if len(config) > 1 {
+		filepath, _ := config[0].(string)
+		r, _ = os.OpenFile(filepath, os.O_WRONLY|os.O_TRUNC, 0x644)
+		configObj = config[1]
+	} else {
+		r, _ = os.OpenFile("./config.json", os.O_WRONLY|os.O_TRUNC, 0x644)
+		configObj = config[0]
+	}
+	defer r.Close()
+	if s, ok := configObj.(string); ok {
+		r.Write([]byte(s))
+
+	} else {
+		bs, _ := json.Marshal(configObj)
+		r.Write(bs)
+	}
+}
+
+var WxConfig wxCfg
+var Mux *Weixin

+ 58 - 0
weixin/httpHandle.go

@@ -0,0 +1,58 @@
+package weixin
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"strings"
+)
+
+func CreateMenu(rw http.ResponseWriter, r *http.Request) {
+	url1 := "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + WxConfig.AppId + "&redirect_uri=http%3A%2F%2F" + WxConfig.Domain + "%2Fwx%2Fsso%3Fcallback%3Dhttp%3A%2F%2F" + WxConfig.Domain + "%2Fkjj%2Fbusiness1&response_type=code&scope=snsapi_base&state=1#wechat_redirect"
+	menu := &Menu{make([]MenuButton, 2)}
+	menu.Buttons[0].Name = "业务1"
+	menu.Buttons[0].Type = MenuButtonTypeUrl
+	menu.Buttons[0].Url = url1
+
+	url2 := "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + WxConfig.AppId + "&redirect_uri=http%3A%2F%2F" + WxConfig.Domain + "%2Fwx%2Fsso%3Fcallback%3Dhttp%3A%2F%2F" + WxConfig.Domain + "%2Fkjj%2Fbusiness2&response_type=code&scope=snsapi_base&state=1#wechat_redirect"
+	menu.Buttons[1].Name = "业务2"
+	menu.Buttons[1].Type = MenuButtonTypeUrl
+	menu.Buttons[1].Url = url2
+
+	err := Mux.CreateMenu(menu)
+	if err != nil {
+		fmt.Fprint(rw, err)
+	} else {
+		fmt.Fprint(rw, "菜单创建成功!")
+	}
+}
+
+func Sso(w http.ResponseWriter, r *http.Request) {
+	uri := r.RequestURI
+	if strings.Index(uri, "?") < 0 {
+		return
+	}
+	r.ParseForm()
+	code, _ := r.Form["code"][0], r.Form["state"][0]
+	callBackUrl := r.Form["callback"][0]
+	url := fmt.Sprintf(WxOpenAuthUrl, WxConfig.AppId, WxConfig.Secret, code)
+	resp, err := http.Get(url)
+	defer resp.Body.Close()
+	if err == nil {
+		//取得openid
+		bs, _ := ioutil.ReadAll(resp.Body)
+		tmp := map[string]interface{}{}
+		err = json.Unmarshal(bs, &tmp)
+		if err != nil {
+			fmt.Fprintln(w, ErrorTemplate)
+			return
+		}
+		openid := ""
+		if tmp["openid"] != nil {
+			openid = tmp["openid"].(string)
+		}
+		http.Redirect(w, r, callBackUrl+"?openid="+openid, http.StatusFound)
+		return
+	}
+}

+ 1 - 0
weixin/muxEventHandle.go

@@ -0,0 +1 @@
+package weixin

+ 25 - 1
weixin.go → weixin/weixin.go

@@ -1,4 +1,4 @@
-package wx_util
+package weixin
 
 import (
 	"bytes"
@@ -1301,3 +1301,27 @@ func (w responseWriter) UploadMedia(mediaType string, filename string, reader io
 func (w responseWriter) DownloadMedia(mediaID string, writer io.Writer) error {
 	return w.wx.DownloadMedia(mediaID, writer)
 }
+
+/*************************************
+			以下为自定义扩展
+**************************************/
+func (wx *Weixin) PostCustomMsg(url string, obj interface{}) (bs []byte, err error) {
+	var data []byte
+	data, err = json.Marshal(obj)
+	if err != nil {
+		return
+	}
+	bs, err = postRequest(url, wx.tokenChan, data)
+	return
+}
+
+//Post custom message(消息结构体完全由开发人员自定义)
+func (wx *Weixin) PostTextCustom(url string, obj interface{}) error {
+	data, err := json.Marshal(obj)
+	if err != nil {
+		return err
+	}
+	log.Println("custom msg:", string(data))
+	_, err = postRequest(url, wx.tokenChan, data)
+	return err
+}

+ 11 - 0
wxCfg.json

@@ -0,0 +1,11 @@
+{
+  "app_id": "wx713ff9dffb034538",
+  "secret": "8e0d77002570458372e644cb23b513e5",
+  "prefix": "/wx/service",
+  "token": "spirit20200312",
+  "domain": "39.107.203.162",
+  "rpc_port": "8083",
+  "message_tpl": {
+    "entService": "euAy8tu2RhgFm-xdA3MTKge5gxQsr0aVQgkYt48ny9E"
+  }
+}

+ 1 - 538
接口说明.md

@@ -1,538 +1 @@
-```GO
-package wx_util // import "app.yhyue.com/BP/weixin_util"
-
-
-CONSTANTS
-
-const (
-	// Event type
-	msgEvent = "event"
-
-	EventSubscribe    = "subscribe"
-	EventUnsubscribe  = "unsubscribe"
-	EventScan         = "SCAN"
-	EventView         = "VIEW"
-	EventClick        = "CLICK"
-	EventLocation     = "LOCATION"
-	EventTemplateSent = "TEMPLATESENDJOBFINISH"
-
-	// Message type
-	MsgTypeDefault           = ".*"
-	MsgTypeText              = "text"
-	MsgTypeImage             = "image"
-	MsgTypeVoice             = "voice"
-	MsgTypeVideo             = "video"
-	MsgTypeShortVideo        = "shortvideo"
-	MsgTypeLocation          = "location"
-	MsgTypeLink              = "link"
-	MsgTypeEvent             = msgEvent + ".*"
-	MsgTypeEventSubscribe    = msgEvent + "\\." + EventSubscribe
-	MsgTypeEventUnsubscribe  = msgEvent + "\\." + EventUnsubscribe
-	MsgTypeEventScan         = msgEvent + "\\." + EventScan
-	MsgTypeEventView         = msgEvent + "\\." + EventView
-	MsgTypeEventClick        = msgEvent + "\\." + EventClick
-	MsgTypeEventLocation     = msgEvent + "\\." + EventLocation
-	MsgTypeEventTemplateSent = msgEvent + "\\." + EventTemplateSent
-
-	// Media type
-	MediaTypeImage = "image"
-	MediaTypeVoice = "voice"
-	MediaTypeVideo = "video"
-	MediaTypeThumb = "thumb"
-	// Button type
-	MenuButtonTypeKey             = "click"
-	MenuButtonTypeUrl             = "view"
-	MenuButtonTypeScancodePush    = "scancode_push"
-	MenuButtonTypeScancodeWaitmsg = "scancode_waitmsg"
-	MenuButtonTypePicSysphoto     = "pic_sysphoto"
-	MenuButtonTypePicPhotoOrAlbum = "pic_photo_or_album"
-	MenuButtonTypePicWeixin       = "pic_weixin"
-	MenuButtonTypeLocationSelect  = "location_select"
-	MenuButtonTypeMediaId         = "media_id"
-	MenuButtonTypeViewLimited     = "view_limited"
-	MenuButtonTypeMiniProgram     = "miniprogram"
-	// Template Status
-	TemplateSentStatusSuccess      = "success"
-	TemplateSentStatusUserBlock    = "failed:user block"
-	TemplateSentStatusSystemFailed = "failed:system failed"
-	// Redirect Scope
-	RedirectURLScopeBasic    = "snsapi_base"
-	RedirectURLScopeUserInfo = "snsapi_userinfo"
-	// Weixin host URL
-	weixinHost               = "https://api.weixin.qq.com/cgi-bin"
-	weixinQRScene            = "https://api.weixin.qq.com/cgi-bin/qrcode"
-	weixinShowQRScene        = "https://mp.weixin.qq.com/cgi-bin/showqrcode"
-	weixinMaterialURL        = "https://api.weixin.qq.com/cgi-bin/material"
-	weixinShortURL           = "https://api.weixin.qq.com/cgi-bin/shorturl"
-	weixinUserInfo           = "https://api.weixin.qq.com/cgi-bin/user/info"
-	weixinFileURL            = "http://file.api.weixin.qq.com/cgi-bin/media"
-	weixinTemplate           = "https://api.weixin.qq.com/cgi-bin/template"
-	weixinRedirectURL        = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect"
-	weixinUserAccessTokenURL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code"
-	weixinJsApiTicketURL     = "https://api.weixin.qq.com/cgi-bin/ticket/getticket"
-	// Max retry count
-	retryMaxN = 3
-	// Reply format
-	replyText               = "<xml>%s<MsgType><![CDATA[text]]></MsgType><Content><![CDATA[%s]]></Content></xml>"
-	replyImage              = "<xml>%s<MsgType><![CDATA[image]]></MsgType><Image><MediaId><![CDATA[%s]]></MediaId></Image></xml>"
-	replyVoice              = "<xml>%s<MsgType><![CDATA[voice]]></MsgType><Voice><MediaId><![CDATA[%s]]></MediaId></Voice></xml>"
-	replyVideo              = "<xml>%s<MsgType><![CDATA[video]]></MsgType><Video><MediaId><![CDATA[%s]]></MediaId><Title><![CDATA[%s]]></Title><Description><![CDATA[%s]]></Description></Video></xml>"
-	replyMusic              = "<xml>%s<MsgType><![CDATA[music]]></MsgType><Music><Title><![CDATA[%s]]></Title><Description><![CDATA[%s]]></Description><MusicUrl><![CDATA[%s]]></MusicUrl><HQMusicUrl><![CDATA[%s]]></HQMusicUrl><ThumbMediaId><![CDATA[%s]]></ThumbMediaId></Music></xml>"
-	replyNews               = "<xml>%s<MsgType><![CDATA[news]]></MsgType><ArticleCount>%d</ArticleCount><Articles>%s</Articles></xml>"
-	replyHeader             = "<ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%d</CreateTime>"
-	replyArticle            = "<item><Title><![CDATA[%s]]></Title> <Description><![CDATA[%s]]></Description><PicUrl><![CDATA[%s]]></PicUrl><Url><![CDATA[%s]]></Url></item>"
-	transferCustomerService = "<xml>" + replyHeader + "<MsgType><![CDATA[transfer_customer_service]]></MsgType></xml>"
-
-	// Material request
-	requestMaterial = `{"type":"%s","offset":%d,"count":%d}`
-	// QR scene request
-	requestQRScene         = `{"expire_seconds":%d,"action_name":"QR_SCENE","action_info":{"scene":{"scene_id":%d}}}`
-	requestQRSceneStr      = `{"expire_seconds":%d,"action_name":"QR_STR_SCENE","action_info":{"scene":{"scene_str":"%s"}}}`
-	requestQRLimitScene    = `{"action_name":"QR_LIMIT_SCENE","action_info":{"scene":{"scene_id":%d}}}`
-	requestQRLimitSceneStr = `{"action_name":"QR_LIMIT_STR_SCENE","action_info":{"scene":{"scene_str":"%s"}}}`
-)
-    nolint
-
-
-FUNCTIONS
-
-func authAccessToken(appid string, secret string) (string, time.Duration)
-func checkSignature(t string, w http.ResponseWriter, r *http.Request) bool
-func createJsAPITicket(cin chan AccessToken, c chan jsAPITicket)
-func downloadMedia(c chan AccessToken, mediaID string, writer io.Writer) error
-func fixPKCS7UnPadding(data []byte) []byte
-func marshal(v interface{}) ([]byte, error)
-func postMessage(c chan AccessToken, msg interface{}) error
-func postRequest(reqURL string, c chan AccessToken, data []byte) ([]byte, error)
-func sendGetRequest(reqURL string, c chan AccessToken) ([]byte, error)
-func uploadMedia(c chan AccessToken, mediaType string, filename string, reader io.Reader) (string, error)
-    nolint: gocyclo
-
-
-TYPES
-
-type AccessToken struct {
-	Token   string
-	Expires time.Time
-}
-    AccessToken define weixin access token.
-
-type Article struct {
-	Title       string `json:"title"`
-	Description string `json:"description"`
-	PicUrl      string `json:"picurl"` // nolint
-	Url         string `json:"url"`    // nolint
-}
-    Article is the response of news message.
-
-type HandlerFunc func(ResponseWriter, *Request)
-    HandlerFunc is callback function handler
-
-type Material struct {
-	MediaId    string `json:"media_id,omitempty"` // nolint
-	Name       string `json:"name,omitempty"`
-	UpdateTime int64  `json:"update_time,omitempty"`
-	CreateTime int64  `json:"create_time,omitempty"`
-	Url        string `json:"url,omitempty"` // nolint
-	Content    struct {
-		NewsItem []struct {
-			Title            string `json:"title,omitempty"`
-			ThumbMediaId     string `json:"thumb_media_id,omitempty"` // nolint
-			ShowCoverPic     int    `json:"show_cover_pic,omitempty"`
-			Author           string `json:"author,omitempty"`
-			Digest           string `json:"digest,omitempty"`
-			Content          string `json:"content,omitempty"`
-			Url              string `json:"url,omitempty"`                // nolint
-			ContentSourceUrl string `json:"content_source_url,omitempty"` // nolint
-		} `json:"news_item,omitempty"`
-	} `json:"content,omitempty"`
-}
-    Material data.
-
-type Materials struct {
-	TotalCount int        `json:"total_count,omitempty"`
-	ItemCount  int        `json:"item_count,omitempty"`
-	Items      []Material `json:"item,omitempty"`
-}
-    Materials is the list of material
-
-type Menu struct {
-	Buttons []MenuButton `json:"button,omitempty"`
-}
-    Menu is custom menu.
-
-type MenuButton struct {
-	Name       string       `json:"name"`
-	Type       string       `json:"type,omitempty"`
-	Key        string       `json:"key,omitempty"`
-	Url        string       `json:"url,omitempty"`      // nolint
-	MediaId    string       `json:"media_id,omitempty"` // nolint
-	SubButtons []MenuButton `json:"sub_button,omitempty"`
-	AppId      string       `json:"appid,omitempty"` // nolint
-	PagePath   string       `json:"pagepath,omitempty"`
-}
-    MenuButton is the button of custom menu.
-
-type MessageHeader struct {
-	ToUserName   string
-	FromUserName string
-	CreateTime   int
-	MsgType      string
-	Encrypt      string
-}
-    MessageHeader is the header of common message.
-
-type Music struct {
-	Title        string `json:"title"`
-	Description  string `json:"description"`
-	MusicUrl     string `json:"musicurl"`       // nolint
-	HQMusicUrl   string `json:"hqmusicurl"`     // nolint
-	ThumbMediaId string `json:"thumb_media_id"` // nolint
-}
-    Music is the response of music message.
-
-type QRScene struct {
-	Ticket        string `json:"ticket"`
-	ExpireSeconds int    `json:"expire_seconds"`
-	Url           string `json:"url,omitempty"` // nolint
-}
-    QRScene is the QR code.
-
-func (qr *QRScene) ToURL() string
-    ToURL convert qr scene to url.
-
-type Request struct {
-	MessageHeader
-	MsgId        int64 // nolint
-	Content      string
-	PicUrl       string // nolint
-	MediaId      string // nolint
-	Format       string
-	ThumbMediaId string  // nolint
-	LocationX    float32 `xml:"Location_X"`
-	LocationY    float32 `xml:"Location_Y"`
-	Scale        float32
-	Label        string
-	Title        string
-	Description  string
-	Url          string // nolint
-	Event        string
-	EventKey     string
-	Ticket       string
-	Latitude     float32
-	Longitude    float32
-	Precision    float32
-	Recognition  string
-	Status       string
-}
-    Request is weixin event request.
-
-type ResponseWriter interface {
-	// Get weixin
-	GetWeixin() *Weixin
-	GetUserData() interface{}
-	// Reply message
-	replyMsg(msg string)
-	ReplyOK()
-	ReplyText(text string)
-	ReplyImage(mediaId string)
-	ReplyVoice(mediaId string)
-	ReplyVideo(mediaId string, title string, description string)
-	ReplyMusic(music *Music)
-	ReplyNews(articles []Article)
-	TransferCustomerService(serviceId string)
-	// Post message
-	PostText(text string) error
-	PostImage(mediaId string) error
-	PostVoice(mediaId string) error
-	PostVideo(mediaId string, title string, description string) error
-	PostMusic(music *Music) error
-	PostNews(articles []Article) error
-	PostTemplateMessage(templateid string, url string, data TmplData) (int32, error)
-	// Media operator
-	UploadMediaFromFile(mediaType string, filepath string) (string, error)
-	DownloadMediaToFile(mediaId string, filepath string) error
-	UploadMedia(mediaType string, filename string, reader io.Reader) (string, error)
-	DownloadMedia(mediaId string, writer io.Writer) error
-}
-    ResponseWriter is used to output reply nolint
-
-type TmplData map[string]TmplItem
-    TmplData for mini program
-
-type TmplItem struct {
-	Value string `json:"value,omitempty"`
-	Color string `json:"color,omitempty"`
-}
-    TmplItem for mini program
-
-type TmplMiniProgram struct {
-	AppId    string `json:"appid,omitempty"` // nolint
-	PagePath string `json:"pagepath,omitempty"`
-}
-    TmplMiniProgram for mini program
-
-type TmplMsg struct {
-	ToUser      string           `json:"touser"`
-	TemplateId  string           `json:"template_id"`           // nolint
-	Url         string           `json:"url,omitempty"`         // nolint 若填写跳转小程序 则此为版本过低的替代跳转url
-	MiniProgram *TmplMiniProgram `json:"miniprogram,omitempty"` // 跳转小程序 选填
-	Data        TmplData         `json:"data,omitempty"`
-	Color       string           `json:"color,omitempty"` // 全局颜色
-}
-    TmplMsg for mini program
-
-type UserAccessToken struct {
-	AccessToken   string `json:"access_token"`
-	RefreshToken  string `json:"refresh_token"`
-	ExpireSeconds int    `json:"expires_in"`
-	OpenId        string `json:"openid"` // nolint
-	Scope         string `json:"scope"`
-	UnionId       string `json:"unionid,omitempty"` // nolint
-}
-    UserAccessToken access token for user.
-
-type UserInfo struct {
-	Subscribe     int    `json:"subscribe,omitempty"`
-	Language      string `json:"language,omitempty"`
-	OpenId        string `json:"openid,omitempty"`  // nolint
-	UnionId       string `json:"unionid,omitempty"` // nolint
-	Nickname      string `json:"nickname,omitempty"`
-	Sex           int    `json:"sex,omitempty"`
-	City          string `json:"city,omitempty"`
-	Country       string `json:"country,omitempty"`
-	Province      string `json:"province,omitempty"`
-	HeadImageUrl  string `json:"headimgurl,omitempty"` // nolint
-	SubscribeTime int64  `json:"subscribe_time,omitempty"`
-	Remark        string `json:"remark,omitempty"`
-	GroupId       int    `json:"groupid,omitempty"` // nolint
-}
-    UserInfo store user information.
-
-type Weixin struct {
-	token          string
-	routes         []*route
-	tokenChan      chan AccessToken
-	ticketChan     chan jsAPITicket
-	userData       interface{}
-	appID          string
-	appSecret      string
-	refreshToken   int32
-	encodingAESKey []byte
-}
-    Weixin instance
-
-func New(token string, appid string, secret string) *Weixin
-    New create a Weixin instance.
-
-func NewWithUserData(token string, appid string, secret string, userData interface{}) *Weixin
-    NewWithUserData create data with userdata.
-
-func (wx *Weixin) AddTemplate(shortid string) (string, error)
-    AddTemplate used to add template.
-
-func (wx *Weixin) BatchGetMaterial(materialType string, offset int, count int) (*Materials, error)
-    BatchGetMaterial used to batch get Material.
-
-func (wx *Weixin) CreateHandlerFunc(w http.ResponseWriter, r *http.Request) http.HandlerFunc
-    CreateHandlerFunc used to create handler function.
-
-func (wx *Weixin) CreateMenu(menu *Menu) error
-    CreateMenu used to create custom menu.
-
-func (wx *Weixin) CreateQRLimitScene(sceneID int) (*QRScene, error)
-    CreateQRLimitScene used to create QR limit scene.
-
-func (wx *Weixin) CreateQRLimitSceneByString(sceneStr string) (*QRScene, error)
-    CreateQRLimitSceneByString used to create QR limit scene by str.
-
-func (wx *Weixin) CreateQRScene(sceneID int, expires int) (*QRScene, error)
-    CreateQRScene used to create QR scene.
-
-func (wx *Weixin) CreateQRSceneByString(sceneStr string, expires int) (*QRScene, error)
-    CreateQRSceneByString used to create QR scene by str.
-
-func (wx *Weixin) CreateRedirectURL(urlStr string, scope string, state string) string
-    CreateRedirectURL used to create redirect url
-
-func (wx *Weixin) DeleteMenu() error
-    DeleteMenu used to delete menu.
-
-func (wx *Weixin) DownloadMedia(mediaID string, writer io.Writer) error
-    DownloadMedia used to download media with media.
-
-func (wx *Weixin) DownloadMediaToFile(mediaID string, fp string) error
-    DownloadMediaToFile used to download media and save to local file.
-
-func (wx *Weixin) GetAccessToken() AccessToken
-    GetAccessToken read access token.
-
-func (wx *Weixin) GetAppId() string
-    GetAppId retrun app id.
-
-func (wx *Weixin) GetAppSecret() string
-    GetAppSecret return app secret.
-
-func (wx *Weixin) GetIpList() ([]string, error)
-    GetIpList used to get ip list.
-
-func (wx *Weixin) GetJsAPITicket() (string, error)
-    GetJsAPITicket used to get js api ticket.
-
-func (wx *Weixin) GetMenu() (*Menu, error)
-    GetMenu used to get menu.
-
-func (wx *Weixin) GetUserAccessToken(code string) (*UserAccessToken, error)
-    GetUserAccessToken used to get open id
-
-func (wx *Weixin) GetUserInfo(openid string) (*UserInfo, error)
-    GetUserInfo used to get user info
-
-func (wx *Weixin) HandleFunc(pattern string, handler HandlerFunc)
-    HandleFunc used to register request callback.
-
-func (wx *Weixin) JsSignature(url string, timestamp int64, noncestr string) (string, error)
-    JsSignature used to sign js url.
-
-func (wx *Weixin) PostImage(touser string, mediaID string) error
-    PostImage used to post image message.
-
-func (wx *Weixin) PostMusic(touser string, music *Music) error
-    PostMusic used to post music message.
-
-func (wx *Weixin) PostNews(touser string, articles []Article) error
-    PostNews used to post news message.
-
-func (wx *Weixin) PostTemplateMessage(touser string, templateid string, url string, data TmplData) (int32, error)
-    PostTemplateMessage used to post template message.
-
-func (wx *Weixin) PostTemplateMessageMiniProgram(msg *TmplMsg) (int64, error)
-    PostTemplateMessageMiniProgram 兼容模板消息跳转小程序
-
-func (wx *Weixin) PostText(touser string, text string) error
-    PostText used to post text message.
-
-func (wx *Weixin) PostVideo(touser string, m string, t string, d string) error
-    PostVideo used to post video message.
-
-func (wx *Weixin) PostVoice(touser string, mediaID string) error
-    PostVoice used to post voice message.
-
-func (wx *Weixin) RefreshAccessToken()
-    RefreshAccessToken update access token.
-
-func (wx *Weixin) ServeHTTP(w http.ResponseWriter, r *http.Request)
-    ServeHTTP used to process weixin request and send response.
-
-func (wx *Weixin) SetEncodingAESKey(key string) error
-    SetEncodingAESKey set AES key
-
-func (wx *Weixin) SetTemplateIndustry(id1 string, id2 string) error
-    SetTemplateIndustry used to set template industry.
-
-func (wx *Weixin) ShortURL(url string) (string, error)
-    ShortURL used to convert long url to short url
-
-func (wx *Weixin) UploadMedia(mediaType string, filename string, reader io.Reader) (string, error)
-    UploadMedia used to upload media with media.
-
-func (wx *Weixin) UploadMediaFromFile(mediaType string, fp string) (string, error)
-    UploadMediaFromFile used to upload media from local file.
-
-func (wx *Weixin) createAccessToken(c chan AccessToken, appid string, secret string)
-
-func (wx *Weixin) routeRequest(w http.ResponseWriter, r *Request)
-
-type jsAPITicket struct {
-	ticket  string
-	expires time.Time
-}
-
-func getJsAPITicket(c chan AccessToken) (*jsAPITicket, error)
-
-type response struct {
-	ErrorCode    int    `json:"errcode,omitempty"`
-	ErrorMessage string `json:"errmsg,omitempty"`
-}
-
-type responseWriter struct {
-	wx           *Weixin
-	writer       http.ResponseWriter
-	toUserName   string
-	fromUserName string
-}
-
-func (w responseWriter) DownloadMedia(mediaID string, writer io.Writer) error
-    Download media with writer
-
-func (w responseWriter) DownloadMediaToFile(mediaID string, filepath string) error
-    Download media and save to local file
-
-func (w responseWriter) GetUserData() interface{}
-    Return user data.
-
-func (w responseWriter) GetWeixin() *Weixin
-    Return weixin instance.
-
-func (w responseWriter) PostImage(mediaID string) error
-    Post image message
-
-func (w responseWriter) PostMusic(music *Music) error
-    Post music message
-
-func (w responseWriter) PostNews(articles []Article) error
-    Post news message
-
-func (w responseWriter) PostTemplateMessage(templateid string, url string, data TmplData) (int32, error)
-    Post template message
-
-func (w responseWriter) PostText(text string) error
-    PostText used to Post text message
-
-func (w responseWriter) PostVideo(mediaID string, title string, desc string) error
-    Post video message
-
-func (w responseWriter) PostVoice(mediaID string) error
-    Post voice message
-
-func (w responseWriter) ReplyImage(mediaID string)
-    ReplyImage used to reply image message.
-
-func (w responseWriter) ReplyMusic(m *Music)
-    ReplyMusic used to reply music message
-
-func (w responseWriter) ReplyNews(articles []Article)
-    ReplyNews used to reply news message (max 10 news)
-
-func (w responseWriter) ReplyOK()
-    ReplyOK used to reply empty message.
-
-func (w responseWriter) ReplyText(text string)
-    ReplyText used to reply text message.
-
-func (w responseWriter) ReplyVideo(mediaID string, title string, description string)
-    ReplyVideo used to reply video message
-
-func (w responseWriter) ReplyVoice(mediaID string)
-    ReplyVoice used to reply voice message.
-
-func (w responseWriter) TransferCustomerService(serviceID string)
-    TransferCustomerService used to tTransfer customer service
-
-func (w responseWriter) UploadMedia(mediaType string, filename string, reader io.Reader) (string, error)
-    Upload media with reader
-
-func (w responseWriter) UploadMediaFromFile(mediaType string, filepath string) (string, error)
-    Upload media from local file
-
-func (w responseWriter) replyHeader() string
-    Format reply message header.
-
-func (w responseWriter) replyMsg(msg string)
-
-type route struct {
-	regex   *regexp.Regexp
-	handler HandlerFunc
-}
-
-```
+## 帮助