From f09af81be02fa871e08039d2a41df63dc7702b1e Mon Sep 17 00:00:00 2001 From: wenzl Date: Fri, 16 Sep 2016 16:39:07 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + README.md | 481 +++++++++++++++++++++++++++++++++++++--- examples/beego/beego.go | 45 ++++ examples/gin/gin.go | 47 ++++ examples/http/http.go | 48 ++++ message/message.go | 24 +- wechat.go | 10 +- 7 files changed, 621 insertions(+), 35 deletions(-) create mode 100644 examples/beego/beego.go create mode 100644 examples/gin/gin.go create mode 100644 examples/http/http.go diff --git a/.gitignore b/.gitignore index daf913b1b..eb1369fd4 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ _testmain.go *.exe *.test *.prof +.DS_Store diff --git a/README.md b/README.md index 9d860be92..858c7afb2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # WeChat SDK for Go +[![Build Status](https://travis-ci.org/silenceper/wechat.svg?branch=develop)](https://travis-ci.org/silenceper/wechat) 使用Golang开发的微信SDK,简单、易用。 @@ -7,44 +8,474 @@ 以下是一个处理消息接收以及回复的例子: ```go +//配置微信参数 +config := &wechat.Config{ + AppID: "xxxx", + AppSecret: "xxxx", + Token: "xxxx", + EncodingAESKey: "xxxx", +} +wc := wechat.NewWechat(config) - //配置微信参数 - config := &wechat.Config{ - AppID: "xxxx", - AppSecret: "xxxx", - Token: "xxxx", - EncodingAESKey: "xxxx", +// 传入request和responseWriter +server := wc.GetServer(request, responseWriter) +server.SetMessageHandler(func(msg message.MixMessage) *message.Reply { + + //回复消息:演示回复用户发送的消息 + text := message.NewText(msg.Content) + return &message.Reply{message.MsgText, text} +}) + +server.Serve() +server.Send() + +``` +完整代码:[examples/http/http.go](./examples/http/http.go) + +#### 和主流框架配合使用 + +主要是request和responseWriter在不同框架中获取方式可能不一样: + +- Beego配合使用 [./examples/beego/beego.go](./examples/beego/beego.go) +- GinFrameworks [./examples/gin/gin.go](./examples/gin/gin.go) + +## 基本API使用 + +## 消息管理 + +通过`wechat.GetServer(request,responseWriter)`获取到server对象之后 + +调用`SetMessageHandler(func(msg message.MixMessage){})`设置消息的处理函数,函数参数为message.MixMessage 结构如下: + +```go +//MixMessage 存放所有微信发送过来的消息和事件 +type MixMessage struct { + CommonToken + + //基本消息 + MsgID int64 `xml:"MsgId"` + Content string `xml:"Content"` + PicURL string `xml:"PicUrl"` + MediaID string `xml:"MediaId"` + Format string `xml:"Format"` + ThumbMediaID string `xml:"ThumbMediaId"` + LocationX float64 `xml:"Location_X"` + LocationY float64 `xml:"Location_Y"` + Scale float64 `xml:"Scale"` + Label string `xml:"Label"` + Title string `xml:"Title"` + Description string `xml:"Description"` + URL string `xml:"Url"` + + //事件相关 + Event string `xml:"Event"` + EventKey string `xml:"EventKey"` + Ticket string `xml:"Ticket"` + Latitude string `xml:"Latitude"` + Longitude string `xml:"Longitude"` + Precision string `xml:"Precision"` + + MenuID string `xml:"MenuId"` + + //扫码事件 + ScanCodeInfo struct { + ScanType string `xml:"ScanType"` + ScanResult string `xml:"ScanResult"` + } `xml:"ScanCodeInfo"` + + //发图事件 + SendPicsInfo struct { + Count int32 `xml:"Count"` + PicList []EventPic `xml:"PicList>item"` + } `xml:"SendPicsInfo"` + + //发送地理位置事件 + SendLocationInfo struct { + LocationX float64 `xml:"Location_X"` + LocationY float64 `xml:"Location_Y"` + Scale float64 `xml:"Scale"` + Label string `xml:"Label"` + Poiname string `xml:"Poiname"` } - wc := wechat.NewWechat(config) +} + +``` + +具体参数请参考微信文档:[接收普通消息 +](http://mp.weixin.qq.com/wiki/17/f298879f8fb29ab98b2f2971d42552fd.html) + +### 接收普通消息 +```go +server.SetMessageHandler(func(v message.MixMessage) *message.Reply { + switch v.MsgType { + //文本消息 + case message.MsgTypeText: + //do something + + //图片消息 + case mesage.MsgTypeImage: + //do something + + //语音消息 + case message.MsgTypeVoice: + //do something + + //视频消息 + case message.MsgTypeVideo: + //do something + + //小视频消息 + case message.MsgTypeShortVideo: + //do something + + //地理位置消息 + case message.MsgTypeShortVideo: + //do something + + //链接消息 + case message.MsgTypeLink: + //do something + + //事件推送消息 + case message.MsgTypeEvent: + + } +} + + +``` + + +### 接收事件推送 +```go +//事件推送消息 +case message.MsgTypeEvent: + switch v.Event { + //EventSubscribe 订阅 + case message.EventSubscribe: + //do something + + //取消订阅 + case message.EventUnsubscribe: + //do something + + //用户已经关注公众号,则微信会将带场景值扫描事件推送给开发者 + case message.EventScan: + //do something + + // 上报地理位置事件 + case message.EventLocation: + //do something + + // 点击菜单拉取消息时的事件推送 + case message.EventClick: + //do something + + // 点击菜单跳转链接时的事件推送 + case message.EventView: + //do something + + // 扫码推事件的事件推送 + case message.EventScancodePush: + //do something + + // 扫码推事件且弹出“消息接收中”提示框的事件推送 + case message.EventScancodeWaitmsg: + //do something + + // 弹出系统拍照发图的事件推送 + case message.EventPicSysphoto: + //do something + + // 弹出拍照或者相册发图的事件推送 + case message.EventPicPhotoOrAlbum: + //do something + + // 弹出微信相册发图器的事件推送 + case message.EventPicWeixin: + //do something + + // 弹出地理位置选择器的事件推送 + case message.EventLocationSelect: + //do something + + } + + +``` + + +### 被动回复消息 + +回复消息需要返回 `*message.Reply` 对象结构体如下: + +```go +type Reply struct { + MsgType MsgType //消息类型 + MsgData interface{} //消息结构 +} + +``` +注意:`retrun nil`表示什么也不做 + +#### 回复文本消息 +```go + text := message.NewText("回复文本消息") + return &message.Reply{message.MsgTypeText, text} +``` +#### 回复图片消息 +```go +//mediaID 可通过素材管理-上上传多媒体文件获得 +image :=message.NewVideo("mediaID") +return &message.Reply{message.MsgTypeVideo, image} +``` +#### 回复视频消息 +```go +video := message.NewVideo("mediaID", "视频标题", "视频描述") +return &message.Reply{message.MsgTypeVideo, video} +``` +#### 回复音乐消息 +```go +music := message.NewMusic("title", "description", "musicURL", "hQMusicURL", "thumbMediaID") +return &message.Reply{message.MsgTypeMusic,music} +``` +**字段说明:** + +Title:音乐标题 + +Description:音乐描述 + +MusicURL:音乐链接 + +HQMusicUrl:高质量音乐链接,WIFI环境优先使用该链接播放音乐 + +ThumbMediaId:缩略图的媒体id,通过素材管理接口上传多媒体文件,得到的id + +#### 回复图文消息 + +```go +articles := make([]*message.Article, 1) + +article := new(message.Article) +article.Title = "标题" +article.Description = "描述信息信息信息" +article.PicURL = "http://ww1.sinaimg.cn/large/65209136gw1f7vhjw95eqj20wt0zk40z.jpg" +article.URL = "https://github.com/silenceper/wechat" +articles[0] = article + +news := message.NewNews(articles) +return &message.Reply{message.MsgTypeNews,news} +``` +**字段说明:** + +Title:图文消息标题 + +Description:图文消息描述 + +PicUrl :图片链接,支持JPG、PNG格式,较好的效果为大图360*200,小图200*200 + +Url :点击图文消息跳转链接 + + +## 自定义菜单 + +通过` wechat.GetMenu(req, writer)`获取menu的实例 + +### 自定义菜单创建接口 - // 传入request和responseWriter - server := wc.GetServer(request, responseWriter) - server.SetMessageHandler(func(msg message.MixMessage) *message.Reply { +以下是一个创建二级菜单的例子 - //回复消息:演示回复用户发送的消息 - text := message.NewText(msg.Content) - return &message.Reply{message.MsgText, text} - }) +```go +mu := wc.GetMenu(c.Request, c.Writer) + +buttons := make([]*menu.Button, 1) +btn := new(menu.Button) + +//创建click类型菜单 +btn.SetClickButton("name", "key123") +buttons[0] = btn + +//设置btn为二级菜单 +btn2 := new(menu.Button) +btn2.SetSubButton("subButton", buttons) + +buttons2 := make([]*menu.Button, 1) +buttons2[0] = btn2 + +//发送请求 +err := mu.SetMenu(buttons2) +if err != nil { + fmt.Printf("err= %v", err) + return +} + +``` + +**创建其他类型的菜单:** + +```go +//SetViewButton view类型 +func (btn *Button) SetViewButton(name, url string) + +// SetScanCodePushButton 扫码推事件 +func (btn *Button) SetScanCodePushButton(name, key string) + +//SetScanCodeWaitMsgButton 设置 扫码推事件且弹出"消息接收中"提示框 +func (btn *Button) SetScanCodeWaitMsgButton(name, key string) - server.Serve() - server.Send() +//SetPicSysPhotoButton 设置弹出系统拍照发图按钮 +func (btn *Button) SetPicSysPhotoButton(name, key string) +//SetPicPhotoOrAlbumButton 设置弹出拍照或者相册发图类型按钮 +func (btn *Button) SetPicPhotoOrAlbumButton(name, key string) { + +// SetPicWeixinButton 设置弹出微信相册发图器类型按钮 +func (btn *Button) SetPicWeixinButton(name, key string) + +// SetLocationSelectButton 设置 弹出地理位置选择器 类型按钮 +func (btn *Button) SetLocationSelectButton(name, key string) + +//SetMediaIDButton 设置 下发消息(除文本消息) 类型按钮 +func (btn *Button) SetMediaIDButton(name, mediaID string) + +//SetViewLimitedButton 设置 跳转图文消息URL 类型按钮 +func (btn *Button) SetViewLimitedButton(name, mediaID string) { + +``` + +### 自定义菜单查询接口 + +```go +mu := wc.GetMenu(c.Request, c.Writer) +resMenu,err:=mu.GetMenu() ``` +>返回结果 resMenu 结构参考 ./menu/menu.go 中ResMenu 结构体 -## 更多API使用 +### 自定义菜单删除 + +```go +mu := wc.GetMenu(c.Request, c.Writer) +err:=mu.DeleteMenu() +``` + +### 自定义菜单事件推送 + + 请参考 消息管理 - 事件推送 + +### 个性化菜单接口 +**添加个性化菜单** + +```go + +func (menu *Menu) AddConditional(buttons []*Button, matchRule *MatchRule) error +``` + +**删除个性化菜单** + +```go +//删除个性化菜单 +func (menu *Menu) DeleteConditional(menuID int64) error + +``` +**测试个性化菜单匹配结果** + +```go +//菜单匹配 +func (menu *Menu) MenuTryMatch(userID string) (buttons []Button, err error) { + +``` -[文档地址](https://github.com/gowechat/docs) +**获取公众号菜单配置** + +```go +//获取自定义菜单配置接口 +func (menu *Menu) GetCurrentSelfMenuInfo() (resSelfMenuInfo ResSelfMenuInfo, err error) + +``` + +## 微信网页开发 + +### Oauth2 授权 + +具体授权流程请参考微信文档:[网页授权](http://mp.weixin.qq.com/wiki/4/9ac2e7b1f1d22e9e57260f6553822520.html) + +**1.发起授权** + +```go +oauth := wc.GetOauth(c.Request, c.Writer) +err := oauth.Redirect("跳转的绝对地址", "snsapi_userinfo", "123dd123") +if err != nil { + fmt.Println(err) +} + +``` +> 如果不希望直接跳转,可通过 oauth.GetRedirectURL 获取跳转的url + +**2.通过code换取access_token** + +```go +code := c.Query("code") +resToken, err := oauth.GetUserAccessToken(code) +if err != nil { + fmt.Println(err) + return +} + +``` +**3.拉取用户信息(需scope为 snsapi_userinfo)** + +```go +//getUserInfo +userInfo, err := oauth.GetUserInfo(resToken.AccessToken, resToken.OpenID) +if err != nil { + fmt.Println(err) + return +} +fmt.Println(userInfo) + +``` +**刷新access_token** + +```go +func (oauth *Oauth) RefreshAccessToken(refreshToken string) (result ResAccessToken, err error) + +``` +**检验access_token是否有效** + +```go +func (oauth *Oauth) CheckAccessToken(accessToken, openID string) (b bool, err error) +``` + +### 获取js-sdk配置 + +```go +js := wc.GetJs(c.Request, c.Writer) +cfg, err := js.GetConfig("传入需要的调用js-sdk的url地址") +if err != nil { + fmt.Println(err) + return +} +fmt.Println(cfg) +``` +其中返回的cfg结构体如下: + +```go +type Config struct { + AppID string + TimeStamp int64 + NonceStr string + Signature string +} + +``` -## 已实现的微信平台接口 +## 素材管理 -- 消息管理(事件推送) -- 网页授权Oauth2 ,Js-SDK -- 菜单管理 -- 用户管理 -- 素材管理 +[素材管理API](https://godoc.org/github.com/silenceper/wechat/material#Material) -持续完善中... +更多API使用请参考 godoc : +[https://godoc.org/github.com/silenceper/wechat](https://godoc.org/github.com/silenceper/wechat) ## License diff --git a/examples/beego/beego.go b/examples/beego/beego.go new file mode 100644 index 000000000..5c7abad89 --- /dev/null +++ b/examples/beego/beego.go @@ -0,0 +1,45 @@ +package main + +import ( + "fmt" + + "github.com/astaxie/beego" + "github.com/astaxie/beego/context" + "github.com/silenceper/wechat" + "github.com/silenceper/wechat/message" +) + +func hello(ctx *context.Context) { + //配置微信参数 + config := &wechat.Config{ + AppID: "your app id", + AppSecret: "your app secret", + Token: "your token", + EncodingAESKey: "your encoding aes key", + } + wc := wechat.NewWechat(config) + + // 传入request和responseWriter + server := wc.GetServer(ctx.Request, ctx.ResponseWriter) + //设置接收消息的处理方法 + server.SetMessageHandler(func(msg message.MixMessage) *message.Reply { + + //回复消息:演示回复用户发送的消息 + text := message.NewText(msg.Content) + return &message.Reply{message.MsgTypeText, text} + }) + + //处理消息接收以及回复 + err := server.Serve() + if err != nil { + fmt.Println(err) + return + } + //发送回复的消息 + server.Send() +} + +func main() { + beego.Any("/", hello) + beego.Run(":8001") +} diff --git a/examples/gin/gin.go b/examples/gin/gin.go new file mode 100644 index 000000000..70ae8a0eb --- /dev/null +++ b/examples/gin/gin.go @@ -0,0 +1,47 @@ +package main + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/silenceper/wechat" + "github.com/silenceper/wechat/message" +) + +func main() { + router := gin.Default() + + router.Any("/", hello) + router.Run(":8001") +} + +func hello(c *gin.Context) { + + //配置微信参数 + config := &wechat.Config{ + AppID: "your app id", + AppSecret: "your app secret", + Token: "your token", + EncodingAESKey: "your encoding aes key", + } + wc := wechat.NewWechat(config) + + // 传入request和responseWriter + server := wc.GetServer(c.Request, c.Writer) + //设置接收消息的处理方法 + server.SetMessageHandler(func(msg message.MixMessage) *message.Reply { + + //回复消息:演示回复用户发送的消息 + text := message.NewText(msg.Content) + return &message.Reply{message.MsgTypeText, text} + }) + + //处理消息接收以及回复 + err := server.Serve() + if err != nil { + fmt.Println(err) + return + } + //发送回复的消息 + server.Send() +} diff --git a/examples/http/http.go b/examples/http/http.go new file mode 100644 index 000000000..642ee267f --- /dev/null +++ b/examples/http/http.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" + "net/http" + + "github.com/silenceper/wechat" + "github.com/silenceper/wechat/message" +) + +func hello(rw http.ResponseWriter, req *http.Request) { + + //配置微信参数 + config := &wechat.Config{ + AppID: "your app id", + AppSecret: "your app secret", + Token: "your token", + EncodingAESKey: "your encoding aes key", + } + wc := wechat.NewWechat(config) + + // 传入request和responseWriter + server := wc.GetServer(req, rw) + //设置接收消息的处理方法 + server.SetMessageHandler(func(msg message.MixMessage) *message.Reply { + + //回复消息:演示回复用户发送的消息 + text := message.NewText(msg.Content) + return &message.Reply{message.MsgTypeText, text} + }) + + //处理消息接收以及回复 + err := server.Serve() + if err != nil { + fmt.Println(err) + return + } + //发送回复的消息 + server.Send() +} + +func main() { + http.HandleFunc("/", hello) + err := http.ListenAndServe(":8001", nil) + if err != nil { + fmt.Printf("start server error , err=%v", err) + } +} diff --git a/message/message.go b/message/message.go index e09ccd99c..ce9d2db2e 100644 --- a/message/message.go +++ b/message/message.go @@ -29,21 +29,35 @@ const ( MsgTypeNews = "news" //MsgTypeTransfer 表示消息消息转发到客服 MsgTypeTransfer = "transfer_customer_service" + //MsgTypeEvent 表示事件推送消息 + MsgTypeEvent = "event" ) const ( //EventSubscribe 订阅 EventSubscribe EventType = "subscribe" //EventUnsubscribe 取消订阅 - EventUnsubscribe EventType = "unsubscribe" + EventUnsubscribe = "unsubscribe" //EventScan 用户已经关注公众号,则微信会将带场景值扫描事件推送给开发者 - EventScan EventType = "SCAN" + EventScan = "SCAN" //EventLocation 上报地理位置事件 - EventLocation EventType = "LOCATION" + EventLocation = "LOCATION" //EventClick 点击菜单拉取消息时的事件推送 - EventClick EventType = "CLICK" + EventClick = "CLICK" //EventView 点击菜单跳转链接时的事件推送 - EventView EventType = "VIEW" + EventView = "VIEW" + //EventScancodePush 扫码推事件的事件推送 + EventScancodePush = "scancode_push" + //EventScancodeWaitmsg 扫码推事件且弹出“消息接收中”提示框的事件推送 + EventScancodeWaitmsg = "scancode_waitmsg" + //EventPicSysphoto 弹出系统拍照发图的事件推送 + EventPicSysphoto = "pic_sysphoto" + //EventPicPhotoOrAlbum 弹出拍照或者相册发图的事件推送 + EventPicPhotoOrAlbum = "pic_photo_or_album" + //EventPicWeixin 弹出微信相册发图器的事件推送 + EventPicWeixin = "pic_weixin" + //EventLocationSelect 弹出地理位置选择器的事件推送 + EventLocationSelect = "location_select" ) //MixMessage 存放所有微信发送过来的消息和事件 diff --git a/wechat.go b/wechat.go index 56d684c5a..d2ca35fe5 100644 --- a/wechat.go +++ b/wechat.go @@ -44,33 +44,33 @@ func copyConfigToContext(cfg *Config, context *context.Context) { context.SetJsApiTicketLock(new(sync.RWMutex)) } -//GetServer init +//GetServer 消息管理 func (wc *Wechat) GetServer(req *http.Request, writer http.ResponseWriter) *server.Server { wc.Context.Request = req wc.Context.Writer = writer return server.NewServer(wc.Context) } -//GetMaterial init +//GetMaterial 素材管理 func (wc *Wechat) GetMaterial() *material.Material { return material.NewMaterial(wc.Context) } -//GetOauth init +//GetOauth oauth2网页授权 func (wc *Wechat) GetOauth(req *http.Request, writer http.ResponseWriter) *oauth.Oauth { wc.Context.Request = req wc.Context.Writer = writer return oauth.NewOauth(wc.Context) } -//GetJs init +//GetJs js-sdk配置 func (wc *Wechat) GetJs(req *http.Request, writer http.ResponseWriter) *js.Js { wc.Context.Request = req wc.Context.Writer = writer return js.NewJs(wc.Context) } -//GetMenu init +//GetMenu 菜单管理接口 func (wc *Wechat) GetMenu(req *http.Request, writer http.ResponseWriter) *menu.Menu { wc.Context.Request = req wc.Context.Writer = writer