silenceper 6 سال پیش
والد
کامیت
15ebd71a04
75فایلهای تغییر یافته به همراه985 افزوده شده و 1871 حذف شده
  1. 1 1
      .gitignore
  2. 34 615
      README.md
  3. 5 0
      aispeech/README.md
  4. 0 30
      context/access_token_test.go
  5. 0 221
      context/component_access_token.go
  6. 0 19
      context/component_test.go
  7. 0 58
      context/context.go
  8. 0 76
      context/qy_access_token.go
  9. 0 43
      context/render.go
  10. 0 23
      doc.go
  11. 0 45
      examples/beego/beego.go
  12. 0 47
      examples/gin/gin.go
  13. 0 48
      examples/http/http.go
  14. 3 15
      go.mod
  15. 10 41
      go.sum
  16. 5 0
      minigame/README.md
  17. 5 0
      miniprogram/README.md
  18. 44 32
      miniprogram/analysis.go
  19. 20 4
      miniprogram/sns.go
  20. 15 0
      miniprogram/basic/basic.go
  21. 5 5
      miniprogram/decrypt.go
  22. 12 0
      miniprogram/config/config.go
  23. 4 2
      context/access_token.go
  24. 18 0
      miniprogram/context/context.go
  25. 51 8
      miniprogram/miniprogram.go
  26. 29 16
      miniprogram/qrcode.go
  27. 0 0
      miniprogram/tcb/README.md
  28. 0 0
      miniprogram/tcb/cloudfunction.go
  29. 0 0
      miniprogram/tcb/database.go
  30. 0 0
      miniprogram/tcb/file.go
  31. 3 4
      tcb/tcb.go
  32. 0 95
      oauth/qy_oauth.go
  33. 5 0
      officialaccount/README.md
  34. 15 0
      officialaccount/basic/basic.go
  35. 3 16
      qr/qr.go
  36. 14 0
      officialaccount/config/config.go
  37. 87 0
      officialaccount/context/access_token.go
  38. 31 0
      officialaccount/context/context.go
  39. 0 0
      officialaccount/device/authorize.go
  40. 0 0
      officialaccount/device/bind.go
  41. 1 1
      device/device.go
  42. 0 0
      officialaccount/device/message.go
  43. 0 0
      officialaccount/device/qrcode.go
  44. 3 3
      js/js.go
  45. 1 1
      material/material.go
  46. 0 0
      officialaccount/material/media.go
  47. 0 0
      officialaccount/menu/button.go
  48. 1 1
      menu/menu.go
  49. 2 1
      message/customer_message.go
  50. 0 0
      officialaccount/message/image.go
  51. 1 1
      message/message.go
  52. 0 0
      officialaccount/message/music.go
  53. 0 0
      officialaccount/message/news.go
  54. 0 0
      officialaccount/message/ransfer_customer.go
  55. 0 0
      officialaccount/message/reply.go
  56. 1 1
      message/template.go
  57. 0 0
      officialaccount/message/text.go
  58. 0 0
      officialaccount/message/video.go
  59. 0 0
      officialaccount/message/voice.go
  60. 1 1
      oauth/oauth.go
  61. 94 0
      officialaccount/officialaccount.go
  62. 6 2
      server/server.go
  63. 58 0
      officialaccount/server/util.go
  64. 1 1
      user/user.go
  65. 5 0
      openplatform/README.md
  66. 5 0
      pay/README.md
  67. 9 0
      pay/config/config.go
  68. 15 0
      pay/notify/notify.go
  69. 10 9
      pay/notify_result.go
  70. 220 0
      pay/order/pay.go
  71. 14 268
      pay/pay.go
  72. 28 16
      pay/refund.go
  73. 67 0
      util/param.go
  74. 18 101
      wechat.go
  75. 5 0
      work/README.md

+ 1 - 1
.gitignore

@@ -26,4 +26,4 @@ _testmain.go
 .vscode/
 vendor
 .idea/
-examples/tcb/*
+example/*

+ 34 - 615
README.md

@@ -1,647 +1,66 @@
 # WeChat SDK for Go
-[![Build Status](https://travis-ci.org/silenceper/wechat.svg?branch=master)](https://travis-ci.org/silenceper/wechat)
+[![Build Status](https://travis-ci.org/silenceper/wechat.svg?branch=release-1.3)](https://travis-ci.org/silenceper/wechat)
 [![Go Report Card](https://goreportcard.com/badge/github.com/silenceper/wechat)](https://goreportcard.com/report/github.com/silenceper/wechat)
 [![GoDoc](http://godoc.org/github.com/silenceper/wechat?status.svg)](http://godoc.org/github.com/silenceper/wechat)
 
 使用Golang开发的微信SDK,简单、易用。
 
+
 ## 快速开始
 
-以下是一个处理消息接收以及回复的例子:
+以下是一个微信公众号处理消息接收以及回复的例子:
 
 ```go
 //使用memcache保存access_token,也可选择redis或自定义cache
-memCache=cache.NewMemcache("127.0.0.1:11211")
-
-//配置微信参数
-config := &wechat.Config{
-	AppID:          "xxxx",
-	AppSecret:      "xxxx",
-	Token:          "xxxx",
-	EncodingAESKey: "xxxx",
-	Cache:          memCache
+wc := wechat.NewWechat()
+memory := cache.NewMemory()
+cfg := &offConfig.Config{
+    AppID:     "xxx",
+    AppSecret: "xxx",
+    Token:     "xxx",
+    //EncodingAESKey: "xxxx",
+    Cache: memory,
 }
-wc := wechat.NewWechat(config)
+officialAccount := wc.GetOfficialAccount(cfg)
 
 // 传入request和responseWriter
-server := wc.GetServer(request, responseWriter)
+server := officialAccount.GetServer(req, rw)
+//设置接收消息的处理方法
 server.SetMessageHandler(func(msg message.MixMessage) *message.Reply {
 
-	//回复消息:演示回复用户发送的消息
-	text := message.NewText(msg.Content)
-	return &message.Reply{message.MsgTypeText, text}
+    //回复消息:演示回复用户发送的消息
+    text := message.NewText(msg.Content)
+    return &message.Reply{MsgType: message.MsgTypeText, MsgData: text}
 })
 
-server.Serve()
+//处理消息接收以及回复
+err := server.Serve()
+if err != nil {
+    fmt.Println(err)
+    return
+}
+//发送回复的消息
 server.Send()
 
 ```
-完整代码:[examples/http/http.go](./examples/http/http.go)
 
-#### 和主流框架配合使用
 
-主要是request和responseWriter在不同框架中获取方式可能不一样:
+## 参与贡献
+
+### 目录说明
+- officialaccount: 微信公众号API
+- miniprogram: 小程序API
+- minigame:小游戏API
+- pay:微信支付API
+- opernplatform:开放平台API
+- work:企业微信
+- aispeech:智能对话
 
-- Beego: [./examples/beego/beego.go](./examples/beego/beego.go)
-- Gin Framework: [./examples/gin/gin.go](./examples/gin/gin.go)
 
 ## 交流群:
 ![关注公众号入群交流](https://silenceper.oss-cn-beijing.aliyuncs.com/qrcode/qr_code_study_program_258.jpg)
 >关注公众号并回复“入群”
 
-## 基本配置
-
-```go
-memcache := cache.NewMemcache("127.0.0.1:11211")
-
-wcConfig := &wechat.Config{
-	AppID:          cfg.AppID,
-	AppSecret:      cfg.AppSecret,
-	Token:          cfg.Token,
-	EncodingAESKey: cfg.EncodingAESKey,//消息加解密时用到
-	Cache:          memcache,
-}
-```
-
-**Cache 设置**
-
-Cache主要用来保存全局access_token以及js-sdk中的ticket:
-默认采用memcache存储。当然也可以直接实现`cache/cache.go`中的接口
-
-
-## 基本API使用
-
-- [消息管理](#消息管理)
-	- 接收普通消息
-	- 接收事件推送
-	- 被动回复消息
-		- 回复文本消息
-		- 回复图片消息
-		- 回复视频消息
-		- 回复音乐消息
-		- 回复图文消息
-- [自定义菜单](#自定义菜单)
-	- 自定义菜单创建接口
-	- 自定义菜单查询接口
-	- 自定义菜单删除接口
-	- 自定义菜单事件推送
-	- 个性化菜单接口
-		- 添加个性化菜单
-		- 删除个性化菜单
-		- 测试个性化菜单匹配结果
-	- 获取公众号菜单配置
-- [微信网页开发](#微信网页开发)
-	- Oauth2 授权
-		- 发起授权
-		- 通过code换取access_token
-		- 拉取用户信息
-		- 刷新access_token
-		- 检验access_token是否有效
-	- 获取js-sdk配置
-- [素材管理](#素材管理)
-- [小程序开发](#小程序开发)
-- [小程序-云开发](./tcb)
-
-## 消息管理
-
-通过`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"`
-	}
-}
-
-```
-
-具体参数请参考微信文档:[接收普通消息
-](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 message.MsgTypeImage:
-			//do something
-
-			//语音消息
-		case message.MsgTypeVoice:
-			//do something
-
-			//视频消息
-		case message.MsgTypeVideo:
-			//do something
-
-			//小视频消息
-		case message.MsgTypeShortVideo:
-			//do something
-
-			//地理位置消息
-		case message.MsgTypeLocation:
-			//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{}  //消息结构
-}
-
-```
-注意:`return nil`表示什么也不做
-
-####  回复文本消息
-```go
-	text := message.NewText("回复文本消息")
-	return &message.Reply{message.MsgTypeText, text}
-```
-####  回复图片消息
-```go
-//mediaID 可通过素材管理-上上传多媒体文件获得
-image :=message.NewImage("mediaID")
-return &message.Reply{message.MsgTypeImage, 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()`获取menu的实例
-
-### 自定义菜单创建接口
-
-以下是一个创建二级菜单的例子
-
-```go
-mu := wc.GetMenu()
-
-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)
-
-//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()
-resMenu,err:=mu.GetMenu()
-```
->返回结果 resMenu 结构参考 ./menu/menu.go 中ResMenu 结构体
-
-### 自定义菜单删除接口
-
-```go
-mu := wc.GetMenu()
-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) {
-
-```
-
-### 获取公众号菜单配置
-
-```go
-//获取自定义菜单配置接口
-func (menu *Menu) GetCurrentSelfMenuInfo() (resSelfMenuInfo ResSelfMenuInfo, err error)
-
-```
-
-## 微信网页开发
-
-### Oauth2 授权
-
-具体授权流程请参考微信文档:[网页授权](http://mp.weixin.qq.com/wiki/4/9ac2e7b1f1d22e9e57260f6553822520.html)
-
-**1.发起授权**
-
-```go
-oauth := wc.GetOauth()
-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()
-cfg, err := js.GetConfig("传入需要的调用js-sdk的url地址")
-if err != nil {
-	fmt.Println(err)
-	return
-}
-fmt.Println(cfg)
-```
-其中返回的cfg结构体如下:
-
-```go
-type Config struct {
-	AppID     string `json:"app_id"`
-	Timestamp int64  `json:"timestamp"`
-	NonceStr  string `json:"nonce_str"`
-	Signature string `json:"signature"`
-}
-
-```
-
-## 素材管理
-
-[素材管理API](https://godoc.org/github.com/silenceper/wechat/material#Material)
-
-## 小程序开发
-
-获取小程序操作对象
-
-``` go
-memCache=cache.NewMemcache("127.0.0.1:11211")
-config := &wechat.Config{
-	AppID:     "xxx",
-	AppSecret: "xxx",
-	Cache:     memCache=cache.NewMemcache("127.0.0.1:11211"),
-}
-wc := wechat.NewWechat(config)
-
-wxa := wc.GetMiniProgram()
-```
-
-### 小程序登录凭证校验
-
-``` go
-func (wxa *MiniProgram) Code2Session(jsCode string) (result ResCode2Session, err error)
-```
-
-### 小程序数据统计
-
-**获取用户访问小程序日留存**
-
-``` go
-func (wxa *MiniProgram) GetAnalysisDailyRetain(beginDate, endDate string) (result ResAnalysisRetain, err error)
-```
-
-**获取用户访问小程序月留存**
-
-``` go
-func (wxa *MiniProgram) GetAnalysisMonthlyRetain(beginDate, endDate string) (result ResAnalysisRetain, err error)
-```
-
-**获取用户访问小程序周留存**
-
-``` go
-func (wxa *MiniProgram) GetAnalysisWeeklyRetain(beginDate, endDate string) (result ResAnalysisRetain, err error)
-```
-
-**获取用户访问小程序数据概况**
-
-``` go
-func (wxa *MiniProgram) GetAnalysisDailySummary(beginDate, endDate string) (result ResAnalysisDailySummary, err error)
-```
-
-**获取用户访问小程序数据日趋势**
-
-``` go
-func (wxa *MiniProgram) GetAnalysisDailyVisitTrend(beginDate, endDate string) (result ResAnalysisVisitTrend, err error)
-```
-
-**获取用户访问小程序数据月趋势**
-
-``` go
-func (wxa *MiniProgram) GetAnalysisMonthlyVisitTrend(beginDate, endDate string) (result ResAnalysisVisitTrend, err error)
-```
-
-**获取用户访问小程序数据周趋势**
-
-``` go
-func (wxa *MiniProgram) GetAnalysisWeeklyVisitTrend(beginDate, endDate string) (result ResAnalysisVisitTrend, err error)
-```
-
-**获取小程序新增或活跃用户的画像分布数据**
-
-``` go
-func (wxa *MiniProgram) GetAnalysisUserPortrait(beginDate, endDate string) (result ResAnalysisUserPortrait, err error)
-```
-
-**获取用户小程序访问分布数据**
-
-``` go
-func (wxa *MiniProgram) GetAnalysisVisitDistribution(beginDate, endDate string) (result ResAnalysisVisitDistribution, err error)
-```
-
-**获取小程序页面访问数据**
-
-``` go
-func (wxa *MiniProgram) GetAnalysisVisitPage(beginDate, endDate string) (result ResAnalysisVisitPage, err error)
-```
-
-### 小程序二维码生成
-
-**获取小程序二维码,适用于需要的码数量较少的业务场景**
-
-``` go
-func (wxa *MiniProgram) CreateWXAQRCode(coderParams QRCoder) (response []byte, err error)
-```
-
-**获取小程序码,适用于需要的码数量较少的业务场景**
-
-``` go
-func (wxa *MiniProgram) GetWXACode(coderParams QRCoder) (response []byte, err error)
-```
-
-**获取小程序码,适用于需要的码数量极多的业务场景**
-
-``` go
-func (wxa *MiniProgram) GetWXACodeUnlimit(coderParams QRCoder) (response []byte, err error)
-```
-
-
-更多API使用请参考 godoc :
-[https://godoc.org/github.com/silenceper/wechat](https://godoc.org/github.com/silenceper/wechat)
-
 ## License
 
 Apache License, Version 2.0

+ 5 - 0
aispeech/README.md

@@ -0,0 +1,5 @@
+# 智能对话
+
+[官方文档](https://developers.weixin.qq.com/doc/aispeech/platform/INTERFACEDOCUMENT.html)
+
+## 快速入门

+ 0 - 30
context/access_token_test.go

@@ -1,30 +0,0 @@
-package context
-
-import (
-	"sync"
-	"testing"
-)
-
-func TestContext_SetCustomAccessTokenFunc(t *testing.T) {
-	ctx := Context{
-		accessTokenLock: new(sync.RWMutex),
-	}
-	f := func(ctx *Context) (accessToken string, err error) {
-		return "fake token", nil
-	}
-	ctx.SetGetAccessTokenFunc(f)
-	res, err := ctx.GetAccessToken()
-	if res != "fake token" || err != nil {
-		t.Error("expect fake token but error")
-	}
-}
-
-func TestContext_NoSetCustomAccessTokenFunc(t *testing.T) {
-	ctx := Context{
-		accessTokenLock: new(sync.RWMutex),
-	}
-
-	if ctx.accessTokenFunc != nil {
-		t.Error("error accessTokenFunc")
-	}
-}

+ 0 - 221
context/component_access_token.go

@@ -1,221 +0,0 @@
-package context
-
-import (
-	"encoding/json"
-	"fmt"
-	"time"
-
-	"github.com/silenceper/wechat/util"
-)
-
-const (
-	componentAccessTokenURL = "https://api.weixin.qq.com/cgi-bin/component/api_component_token"
-	getPreCodeURL           = "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=%s"
-	queryAuthURL            = "https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token=%s"
-	refreshTokenURL         = "https://api.weixin.qq.com/cgi-bin/component/api_authorizer_token?component_access_token=%s"
-	getComponentInfoURL     = "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_info?component_access_token=%s"
-	getComponentConfigURL   = "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_option?component_access_token=%s"
-)
-
-// ComponentAccessToken 第三方平台
-type ComponentAccessToken struct {
-	AccessToken string `json:"component_access_token"`
-	ExpiresIn   int64  `json:"expires_in"`
-}
-
-// GetComponentAccessToken 获取 ComponentAccessToken
-func (ctx *Context) GetComponentAccessToken() (string, error) {
-	accessTokenCacheKey := fmt.Sprintf("component_access_token_%s", ctx.AppID)
-	val := ctx.Cache.Get(accessTokenCacheKey)
-	if val == nil {
-		return "", fmt.Errorf("cann't get component access token")
-	}
-	return val.(string), nil
-}
-
-// SetComponentAccessToken 通过component_verify_ticket 获取 ComponentAccessToken
-func (ctx *Context) SetComponentAccessToken(verifyTicket string) (*ComponentAccessToken, error) {
-	body := map[string]string{
-		"component_appid":         ctx.AppID,
-		"component_appsecret":     ctx.AppSecret,
-		"component_verify_ticket": verifyTicket,
-	}
-	respBody, err := util.PostJSON(componentAccessTokenURL, body)
-	if err != nil {
-		return nil, err
-	}
-
-	at := &ComponentAccessToken{}
-	if err := json.Unmarshal(respBody, at); err != nil {
-		return nil, err
-	}
-
-	accessTokenCacheKey := fmt.Sprintf("component_access_token_%s", ctx.AppID)
-	expires := at.ExpiresIn - 1500
-	ctx.Cache.Set(accessTokenCacheKey, at.AccessToken, time.Duration(expires)*time.Second)
-	return at, nil
-}
-
-// GetPreCode 获取预授权码
-func (ctx *Context) GetPreCode() (string, error) {
-	cat, err := ctx.GetComponentAccessToken()
-	if err != nil {
-		return "", err
-	}
-	req := map[string]string{
-		"component_appid": ctx.AppID,
-	}
-	uri := fmt.Sprintf(getPreCodeURL, cat)
-	body, err := util.PostJSON(uri, req)
-	if err != nil {
-		return "", err
-	}
-
-	var ret struct {
-		PreCode string `json:"pre_auth_code"`
-	}
-	if err := json.Unmarshal(body, &ret); err != nil {
-		return "", err
-	}
-
-	return ret.PreCode, nil
-}
-
-// ID 微信返回接口中各种类型字段
-type ID struct {
-	ID int `json:"id"`
-}
-
-// AuthBaseInfo 授权的基本信息
-type AuthBaseInfo struct {
-	AuthrAccessToken
-	FuncInfo []AuthFuncInfo `json:"func_info"`
-}
-
-// AuthFuncInfo 授权的接口内容
-type AuthFuncInfo struct {
-	FuncscopeCategory ID `json:"funcscope_category"`
-}
-
-// AuthrAccessToken 授权方AccessToken
-type AuthrAccessToken struct {
-	Appid        string `json:"authorizer_appid"`
-	AccessToken  string `json:"authorizer_access_token"`
-	ExpiresIn    int64  `json:"expires_in"`
-	RefreshToken string `json:"authorizer_refresh_token"`
-}
-
-// QueryAuthCode 使用授权码换取公众号或小程序的接口调用凭据和授权信息
-func (ctx *Context) QueryAuthCode(authCode string) (*AuthBaseInfo, error) {
-	cat, err := ctx.GetComponentAccessToken()
-	if err != nil {
-		return nil, err
-	}
-
-	req := map[string]string{
-		"component_appid":    ctx.AppID,
-		"authorization_code": authCode,
-	}
-	uri := fmt.Sprintf(queryAuthURL, cat)
-	body, err := util.PostJSON(uri, req)
-	if err != nil {
-		return nil, err
-	}
-
-	var ret struct {
-		Info *AuthBaseInfo `json:"authorization_info"`
-	}
-
-	if err := json.Unmarshal(body, &ret); err != nil {
-		return nil, err
-	}
-
-	return ret.Info, nil
-}
-
-// RefreshAuthrToken 获取(刷新)授权公众号或小程序的接口调用凭据(令牌)
-func (ctx *Context) RefreshAuthrToken(appid, refreshToken string) (*AuthrAccessToken, error) {
-	cat, err := ctx.GetComponentAccessToken()
-	if err != nil {
-		return nil, err
-	}
-
-	req := map[string]string{
-		"component_appid":          ctx.AppID,
-		"authorizer_appid":         appid,
-		"authorizer_refresh_token": refreshToken,
-	}
-	uri := fmt.Sprintf(refreshTokenURL, cat)
-	body, err := util.PostJSON(uri, req)
-	if err != nil {
-		return nil, err
-	}
-
-	ret := &AuthrAccessToken{}
-	if err := json.Unmarshal(body, ret); err != nil {
-		return nil, err
-	}
-
-	authrTokenKey := "authorizer_access_token_" + appid
-	ctx.Cache.Set(authrTokenKey, ret.AccessToken, time.Minute*80)
-
-	return ret, nil
-}
-
-// GetAuthrAccessToken 获取授权方AccessToken
-func (ctx *Context) GetAuthrAccessToken(appid string) (string, error) {
-	authrTokenKey := "authorizer_access_token_" + appid
-	val := ctx.Cache.Get(authrTokenKey)
-	if val == nil {
-		return "", fmt.Errorf("cannot get authorizer %s access token", appid)
-	}
-	return val.(string), nil
-}
-
-// AuthorizerInfo 授权方详细信息
-type AuthorizerInfo struct {
-	NickName        string `json:"nick_name"`
-	HeadImg         string `json:"head_img"`
-	ServiceTypeInfo ID     `json:"service_type_info"`
-	VerifyTypeInfo  ID     `json:"verify_type_info"`
-	UserName        string `json:"user_name"`
-	PrincipalName   string `json:"principal_name"`
-	BusinessInfo    struct {
-		OpenStore string `json:"open_store"`
-		OpenScan  string `json:"open_scan"`
-		OpenPay   string `json:"open_pay"`
-		OpenCard  string `json:"open_card"`
-		OpenShake string `json:"open_shake"`
-	}
-	Alias     string `json:"alias"`
-	QrcodeURL string `json:"qrcode_url"`
-}
-
-// GetAuthrInfo 获取授权方的帐号基本信息
-func (ctx *Context) GetAuthrInfo(appid string) (*AuthorizerInfo, *AuthBaseInfo, error) {
-	cat, err := ctx.GetComponentAccessToken()
-	if err != nil {
-		return nil, nil, err
-	}
-
-	req := map[string]string{
-		"component_appid":  ctx.AppID,
-		"authorizer_appid": appid,
-	}
-
-	uri := fmt.Sprintf(getComponentInfoURL, cat)
-	body, err := util.PostJSON(uri, req)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	var ret struct {
-		AuthorizerInfo    *AuthorizerInfo `json:"authorizer_info"`
-		AuthorizationInfo *AuthBaseInfo   `json:"authorization_info"`
-	}
-	if err := json.Unmarshal(body, &ret); err != nil {
-		return nil, nil, err
-	}
-
-	return ret.AuthorizerInfo, ret.AuthorizationInfo, nil
-}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 19
context/component_test.go


+ 0 - 58
context/context.go

@@ -1,58 +0,0 @@
-package context
-
-import (
-	"net/http"
-	"sync"
-
-	"github.com/silenceper/wechat/cache"
-)
-
-// Context struct
-type Context struct {
-	AppID          string
-	AppSecret      string
-	Token          string
-	EncodingAESKey string
-	PayMchID       string
-	PayNotifyURL   string
-	PayKey         string
-
-	Cache cache.Cache
-
-	Writer  http.ResponseWriter
-	Request *http.Request
-
-	//accessTokenLock 读写锁 同一个AppID一个
-	accessTokenLock *sync.RWMutex
-
-	//jsAPITicket 读写锁 同一个AppID一个
-	jsAPITicketLock *sync.RWMutex
-
-	//accessTokenFunc 自定义获取 access token 的方法
-	accessTokenFunc GetAccessTokenFunc
-}
-
-// Query returns the keyed url query value if it exists
-func (ctx *Context) Query(key string) string {
-	value, _ := ctx.GetQuery(key)
-	return value
-}
-
-// GetQuery is like Query(), it returns the keyed url query value
-func (ctx *Context) GetQuery(key string) (string, bool) {
-	req := ctx.Request
-	if values, ok := req.URL.Query()[key]; ok && len(values) > 0 {
-		return values[0], true
-	}
-	return "", false
-}
-
-// SetJsAPITicketLock 设置jsAPITicket的lock
-func (ctx *Context) SetJsAPITicketLock(lock *sync.RWMutex) {
-	ctx.jsAPITicketLock = lock
-}
-
-// GetJsAPITicketLock 获取jsAPITicket 的lock
-func (ctx *Context) GetJsAPITicketLock() *sync.RWMutex {
-	return ctx.jsAPITicketLock
-}

+ 0 - 76
context/qy_access_token.go

@@ -1,76 +0,0 @@
-package context
-
-import (
-	"encoding/json"
-	"fmt"
-	"log"
-	"sync"
-	"time"
-
-	"github.com/silenceper/wechat/util"
-)
-
-const (
-	//qyAccessTokenURL 获取access_token的接口
-	qyAccessTokenURL = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s"
-)
-
-//ResQyAccessToken struct
-type ResQyAccessToken struct {
-	util.CommonError
-
-	AccessToken string `json:"access_token"`
-	ExpiresIn   int64  `json:"expires_in"`
-}
-
-//SetQyAccessTokenLock 设置读写锁(一个appID一个读写锁)
-func (ctx *Context) SetQyAccessTokenLock(l *sync.RWMutex) {
-	ctx.accessTokenLock = l
-}
-
-//GetQyAccessToken 获取access_token
-func (ctx *Context) GetQyAccessToken() (accessToken string, err error) {
-	ctx.accessTokenLock.Lock()
-	defer ctx.accessTokenLock.Unlock()
-
-	accessTokenCacheKey := fmt.Sprintf("qy_access_token_%s", ctx.AppID)
-	val := ctx.Cache.Get(accessTokenCacheKey)
-	if val != nil {
-		accessToken = val.(string)
-		return
-	}
-
-	//从微信服务器获取
-	var resQyAccessToken ResQyAccessToken
-	resQyAccessToken, err = ctx.GetQyAccessTokenFromServer()
-	if err != nil {
-		return
-	}
-
-	accessToken = resQyAccessToken.AccessToken
-	return
-}
-
-//GetQyAccessTokenFromServer 强制从微信服务器获取token
-func (ctx *Context) GetQyAccessTokenFromServer() (resQyAccessToken ResQyAccessToken, err error) {
-	log.Printf("GetQyAccessTokenFromServer")
-	url := fmt.Sprintf(qyAccessTokenURL, ctx.AppID, ctx.AppSecret)
-	var body []byte
-	body, err = util.HTTPGet(url)
-	if err != nil {
-		return
-	}
-	err = json.Unmarshal(body, &resQyAccessToken)
-	if err != nil {
-		return
-	}
-	if resQyAccessToken.ErrCode != 0 {
-		err = fmt.Errorf("get qy_access_token error : errcode=%v , errormsg=%v", resQyAccessToken.ErrCode, resQyAccessToken.ErrMsg)
-		return
-	}
-
-	qyAccessTokenCacheKey := fmt.Sprintf("qy_access_token_%s", ctx.AppID)
-	expires := resQyAccessToken.ExpiresIn - 1500
-	err = ctx.Cache.Set(qyAccessTokenCacheKey, resQyAccessToken.AccessToken, time.Duration(expires)*time.Second)
-	return
-}

+ 0 - 43
context/render.go

@@ -1,43 +0,0 @@
-package context
-
-import (
-	"encoding/xml"
-	"net/http"
-)
-
-var xmlContentType = []string{"application/xml; charset=utf-8"}
-var plainContentType = []string{"text/plain; charset=utf-8"}
-
-//Render render from bytes
-func (ctx *Context) Render(bytes []byte) {
-	//debug
-	//fmt.Println("response msg = ", string(bytes))
-	ctx.Writer.WriteHeader(200)
-	_, err := ctx.Writer.Write(bytes)
-	if err != nil {
-		panic(err)
-	}
-}
-
-//String render from string
-func (ctx *Context) String(str string) {
-	writeContextType(ctx.Writer, plainContentType)
-	ctx.Render([]byte(str))
-}
-
-//XML render to xml
-func (ctx *Context) XML(obj interface{}) {
-	writeContextType(ctx.Writer, xmlContentType)
-	bytes, err := xml.Marshal(obj)
-	if err != nil {
-		panic(err)
-	}
-	ctx.Render(bytes)
-}
-
-func writeContextType(w http.ResponseWriter, value []string) {
-	header := w.Header()
-	if val := header["Content-Type"]; len(val) == 0 {
-		header["Content-Type"] = value
-	}
-}

+ 0 - 23
doc.go

@@ -3,29 +3,6 @@ Package wechat provide wechat sdk for go
 
 使用Golang开发的微信SDK,简单、易用。
 
-以下是一个处理消息接收以及回复的例子:
-
-	//配置微信参数
-	config := &wechat.Config{
-		AppID:          "xxxx",
-		AppSecret:      "xxxx",
-		Token:          "xxxx",
-		EncodingAESKey: "xxxx",
-	}
-	wc := wechat.NewWechat(config)
-
-	// 传入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()
-
 
 更多信息:https://github.com/silenceper/wechat
 

+ 0 - 45
examples/beego/beego.go

@@ -1,45 +0,0 @@
-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{MsgType: message.MsgTypeText, MsgData: text}
-	})
-
-	//处理消息接收以及回复
-	err := server.Serve()
-	if err != nil {
-		fmt.Println(err)
-		return
-	}
-	//发送回复的消息
-	server.Send()
-}
-
-func main() {
-	beego.Any("/", hello)
-	beego.Run(":8001")
-}

+ 0 - 47
examples/gin/gin.go

@@ -1,47 +0,0 @@
-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{MsgType: message.MsgTypeText, MsgData: text}
-	})
-
-	//处理消息接收以及回复
-	err := server.Serve()
-	if err != nil {
-		fmt.Println(err)
-		return
-	}
-	//发送回复的消息
-	server.Send()
-}

+ 0 - 48
examples/http/http.go

@@ -1,48 +0,0 @@
-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{MsgType: message.MsgTypeText, MsgData: 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)
-	}
-}

+ 3 - 15
go.mod

@@ -3,19 +3,7 @@ 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
+	github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
+	github.com/gomodule/redigo v2.0.0+incompatible
+	golang.org/x/crypto v0.0.0-20200117160349-530e935923ad
 )

+ 10 - 41
go.sum

@@ -1,44 +1,13 @@
-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=
+github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
+github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
+github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
+github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
 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/crypto v0.0.0-20200117160349-530e935923ad h1:Jh8cai0fqIK+f6nG0UgPW5wFk8wmiMhM3AyciDBdtQg=
+golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 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=

+ 5 - 0
minigame/README.md

@@ -0,0 +1,5 @@
+# 微信小游戏
+
+[官方文档](https://developers.weixin.qq.com/minigame/dev/api-backend/)
+
+## 快速入门

+ 5 - 0
miniprogram/README.md

@@ -0,0 +1,5 @@
+# 微信小程序
+
+[官方文档](https://developers.weixin.qq.com/miniprogram/dev/framework/)
+
+## 快速入门

+ 44 - 32
miniprogram/analysis.go

@@ -1,9 +1,11 @@
-package miniprogram
+package analysis
 
 import (
 	"encoding/json"
 	"fmt"
 
+	"github.com/silenceper/wechat/miniprogram/context"
+
 	"github.com/silenceper/wechat/util"
 )
 
@@ -30,10 +32,20 @@ const (
 	getAnalysisVisitPageURL = "https://api.weixin.qq.com/datacube/getweanalysisappidvisitpage?access_token=%s"
 )
 
+//Analysis analyis 数据分析
+type Analysis struct {
+	*context.Context
+}
+
+//NewAnalysis new
+func NewAnalysis(ctx *context.Context) *Analysis {
+	return &Analysis{ctx}
+}
+
 // fetchData 拉取统计数据
-func (wxa *MiniProgram) fetchData(urlStr string, body interface{}) (response []byte, err error) {
+func (analysis *Analysis) fetchData(urlStr string, body interface{}) (response []byte, err error) {
 	var accessToken string
-	accessToken, err = wxa.GetAccessToken()
+	accessToken, err = analysis.GetAccessToken()
 	if err != nil {
 		return
 	}
@@ -42,8 +54,8 @@ func (wxa *MiniProgram) fetchData(urlStr string, body interface{}) (response []b
 	return
 }
 
-// AnalysisRetainItem 留存项结构
-type AnalysisRetainItem struct {
+// RetainItem 留存项结构
+type RetainItem struct {
 	Key   int `json:"key"`   // 标识,0开始表示当天,1表示1甜后,以此类推
 	Value int `json:"value"` // key对应日期的新增用户数/活跃用户数(key=0时)或留存用户数(k>0时)
 }
@@ -51,18 +63,18 @@ type AnalysisRetainItem struct {
 // ResAnalysisRetain 小程序留存数据返回
 type ResAnalysisRetain struct {
 	util.CommonError
-	RefDate    string               `json:"ref_date"`     // 日期
-	VisitUVNew []AnalysisRetainItem `json:"visit_uv_new"` // 新增用户留存
-	VisitUV    []AnalysisRetainItem `json:"visit_uv"`     // 活跃用户留存
+	RefDate    string       `json:"ref_date"`     // 日期
+	VisitUVNew []RetainItem `json:"visit_uv_new"` // 新增用户留存
+	VisitUV    []RetainItem `json:"visit_uv"`     // 活跃用户留存
 }
 
 // getAnalysisRetain 获取用户访问小程序留存数据(日、月、周)
-func (wxa *MiniProgram) getAnalysisRetain(urlStr string, beginDate, endDate string) (result ResAnalysisRetain, err error) {
+func (analysis *Analysis) getAnalysisRetain(urlStr string, beginDate, endDate string) (result ResAnalysisRetain, err error) {
 	body := map[string]string{
 		"begin_date": beginDate,
 		"end_date":   endDate,
 	}
-	response, err := wxa.fetchData(urlStr, body)
+	response, err := analysis.fetchData(urlStr, body)
 	if err != nil {
 		return
 	}
@@ -78,18 +90,18 @@ func (wxa *MiniProgram) getAnalysisRetain(urlStr string, beginDate, endDate stri
 }
 
 // GetAnalysisDailyRetain 获取用户访问小程序日留存
-func (wxa *MiniProgram) GetAnalysisDailyRetain(beginDate, endDate string) (result ResAnalysisRetain, err error) {
-	return wxa.getAnalysisRetain(getAnalysisDailyRetainURL, beginDate, endDate)
+func (analysis *Analysis) GetAnalysisDailyRetain(beginDate, endDate string) (result ResAnalysisRetain, err error) {
+	return analysis.getAnalysisRetain(getAnalysisDailyRetainURL, beginDate, endDate)
 }
 
 // GetAnalysisMonthlyRetain 获取用户访问小程序月留存
-func (wxa *MiniProgram) GetAnalysisMonthlyRetain(beginDate, endDate string) (result ResAnalysisRetain, err error) {
-	return wxa.getAnalysisRetain(getAnalysisMonthlyRetainURL, beginDate, endDate)
+func (analysis *Analysis) GetAnalysisMonthlyRetain(beginDate, endDate string) (result ResAnalysisRetain, err error) {
+	return analysis.getAnalysisRetain(getAnalysisMonthlyRetainURL, beginDate, endDate)
 }
 
 // GetAnalysisWeeklyRetain 获取用户访问小程序周留存
-func (wxa *MiniProgram) GetAnalysisWeeklyRetain(beginDate, endDate string) (result ResAnalysisRetain, err error) {
-	return wxa.getAnalysisRetain(getAnalysisWeeklyRetainURL, beginDate, endDate)
+func (analysis *Analysis) GetAnalysisWeeklyRetain(beginDate, endDate string) (result ResAnalysisRetain, err error) {
+	return analysis.getAnalysisRetain(getAnalysisWeeklyRetainURL, beginDate, endDate)
 }
 
 // ResAnalysisDailySummary 小程序访问数据概况
@@ -104,12 +116,12 @@ type ResAnalysisDailySummary struct {
 }
 
 // GetAnalysisDailySummary 获取用户访问小程序数据概况
-func (wxa *MiniProgram) GetAnalysisDailySummary(beginDate, endDate string) (result ResAnalysisDailySummary, err error) {
+func (analysis *Analysis) GetAnalysisDailySummary(beginDate, endDate string) (result ResAnalysisDailySummary, err error) {
 	body := map[string]string{
 		"begin_date": beginDate,
 		"end_date":   endDate,
 	}
-	response, err := wxa.fetchData(getAnalysisDailySummaryURL, body)
+	response, err := analysis.fetchData(getAnalysisDailySummaryURL, body)
 	if err != nil {
 		return
 	}
@@ -141,12 +153,12 @@ type ResAnalysisVisitTrend struct {
 }
 
 // getAnalysisRetain 获取小程序访问数据趋势(日、月、周)
-func (wxa *MiniProgram) getAnalysisVisitTrend(urlStr string, beginDate, endDate string) (result ResAnalysisVisitTrend, err error) {
+func (analysis *Analysis) getAnalysisVisitTrend(urlStr string, beginDate, endDate string) (result ResAnalysisVisitTrend, err error) {
 	body := map[string]string{
 		"begin_date": beginDate,
 		"end_date":   endDate,
 	}
-	response, err := wxa.fetchData(urlStr, body)
+	response, err := analysis.fetchData(urlStr, body)
 	if err != nil {
 		return
 	}
@@ -162,18 +174,18 @@ func (wxa *MiniProgram) getAnalysisVisitTrend(urlStr string, beginDate, endDate
 }
 
 // GetAnalysisDailyVisitTrend 获取用户访问小程序数据日趋势
-func (wxa *MiniProgram) GetAnalysisDailyVisitTrend(beginDate, endDate string) (result ResAnalysisVisitTrend, err error) {
-	return wxa.getAnalysisVisitTrend(getAnalysisDailyVisitTrendURL, beginDate, endDate)
+func (analysis *Analysis) GetAnalysisDailyVisitTrend(beginDate, endDate string) (result ResAnalysisVisitTrend, err error) {
+	return analysis.getAnalysisVisitTrend(getAnalysisDailyVisitTrendURL, beginDate, endDate)
 }
 
 // GetAnalysisMonthlyVisitTrend 获取用户访问小程序数据月趋势
-func (wxa *MiniProgram) GetAnalysisMonthlyVisitTrend(beginDate, endDate string) (result ResAnalysisVisitTrend, err error) {
-	return wxa.getAnalysisVisitTrend(getAnalysisMonthlyVisitTrendURL, beginDate, endDate)
+func (analysis *Analysis) GetAnalysisMonthlyVisitTrend(beginDate, endDate string) (result ResAnalysisVisitTrend, err error) {
+	return analysis.getAnalysisVisitTrend(getAnalysisMonthlyVisitTrendURL, beginDate, endDate)
 }
 
 // GetAnalysisWeeklyVisitTrend 获取用户访问小程序数据周趋势
-func (wxa *MiniProgram) GetAnalysisWeeklyVisitTrend(beginDate, endDate string) (result ResAnalysisVisitTrend, err error) {
-	return wxa.getAnalysisVisitTrend(getAnalysisWeeklyVisitTrendURL, beginDate, endDate)
+func (analysis *Analysis) GetAnalysisWeeklyVisitTrend(beginDate, endDate string) (result ResAnalysisVisitTrend, err error) {
+	return analysis.getAnalysisVisitTrend(getAnalysisWeeklyVisitTrendURL, beginDate, endDate)
 }
 
 // UserPortraitItem 用户画像项目
@@ -203,12 +215,12 @@ type ResAnalysisUserPortrait struct {
 }
 
 // GetAnalysisUserPortrait 获取小程序新增或活跃用户的画像分布数据
-func (wxa *MiniProgram) GetAnalysisUserPortrait(beginDate, endDate string) (result ResAnalysisUserPortrait, err error) {
+func (analysis *Analysis) GetAnalysisUserPortrait(beginDate, endDate string) (result ResAnalysisUserPortrait, err error) {
 	body := map[string]string{
 		"begin_date": beginDate,
 		"end_date":   endDate,
 	}
-	response, err := wxa.fetchData(getAnalysisUserPortraitURL, body)
+	response, err := analysis.fetchData(getAnalysisUserPortraitURL, body)
 	if err != nil {
 		return
 	}
@@ -244,12 +256,12 @@ type ResAnalysisVisitDistribution struct {
 }
 
 // GetAnalysisVisitDistribution 获取用户小程序访问分布数据
-func (wxa *MiniProgram) GetAnalysisVisitDistribution(beginDate, endDate string) (result ResAnalysisVisitDistribution, err error) {
+func (analysis *Analysis) GetAnalysisVisitDistribution(beginDate, endDate string) (result ResAnalysisVisitDistribution, err error) {
 	body := map[string]string{
 		"begin_date": beginDate,
 		"end_date":   endDate,
 	}
-	response, err := wxa.fetchData(getAnalysisVisitDistributionURL, body)
+	response, err := analysis.fetchData(getAnalysisVisitDistributionURL, body)
 	if err != nil {
 		return
 	}
@@ -284,12 +296,12 @@ type ResAnalysisVisitPage struct {
 }
 
 // GetAnalysisVisitPage 获取小程序页面访问数据
-func (wxa *MiniProgram) GetAnalysisVisitPage(beginDate, endDate string) (result ResAnalysisVisitPage, err error) {
+func (analysis *Analysis) GetAnalysisVisitPage(beginDate, endDate string) (result ResAnalysisVisitPage, err error) {
 	body := map[string]string{
 		"begin_date": beginDate,
 		"end_date":   endDate,
 	}
-	response, err := wxa.fetchData(getAnalysisVisitPageURL, body)
+	response, err := analysis.fetchData(getAnalysisVisitPageURL, body)
 	if err != nil {
 		return
 	}

+ 20 - 4
miniprogram/sns.go

@@ -1,9 +1,10 @@
-package miniprogram
+package auth
 
 import (
 	"encoding/json"
 	"fmt"
 
+	"github.com/silenceper/wechat/miniprogram/context"
 	"github.com/silenceper/wechat/util"
 )
 
@@ -11,6 +12,16 @@ const (
 	code2SessionURL = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code"
 )
 
+//Auth 登录/用户信息
+type Auth struct {
+	*context.Context
+}
+
+//NewAuth new auth
+func NewAuth(ctx *context.Context) *Auth {
+	return &Auth{ctx}
+}
+
 // ResCode2Session 登录凭证校验的返回结果
 type ResCode2Session struct {
 	util.CommonError
@@ -20,9 +31,9 @@ type ResCode2Session struct {
 	UnionID    string `json:"unionid"`     // 用户在开放平台的唯一标识符,在满足UnionID下发条件的情况下会返回
 }
 
-// Code2Session 登录凭证校验
-func (wxa *MiniProgram) Code2Session(jsCode string) (result ResCode2Session, err error) {
-	urlStr := fmt.Sprintf(code2SessionURL, wxa.AppID, wxa.AppSecret, jsCode)
+//Code2Session 登录凭证校验
+func (auth *Auth) Code2Session(jsCode string) (result ResCode2Session, err error) {
+	urlStr := fmt.Sprintf(code2SessionURL, auth.AppID, auth.AppSecret, jsCode)
 	var response []byte
 	response, err = util.HTTPGet(urlStr)
 	if err != nil {
@@ -38,3 +49,8 @@ func (wxa *MiniProgram) Code2Session(jsCode string) (result ResCode2Session, err
 	}
 	return
 }
+
+//GetPaidUnionID 用户支付完成后,获取该用户的 UnionId,无需用户授权
+func (auth *Auth) GetPaidUnionID() {
+	//TODO
+}

+ 15 - 0
miniprogram/basic/basic.go

@@ -0,0 +1,15 @@
+package basic
+
+import "github.com/silenceper/wechat/miniprogram/context"
+
+//Basic struct
+type Basic struct {
+	*context.Context
+}
+
+//NewBasic 实例
+func NewBasic(context *context.Context) *Basic {
+	basic := new(Basic)
+	basic.Context = context
+	return basic
+}

+ 5 - 5
miniprogram/decrypt.go

@@ -1,4 +1,4 @@
-package miniprogram
+package basic
 
 import (
 	"crypto/aes"
@@ -96,7 +96,7 @@ func getCipherText(sessionKey, encryptedData, iv string) ([]byte, error) {
 }
 
 // Decrypt 解密数据
-func (wxa *MiniProgram) Decrypt(sessionKey, encryptedData, iv string) (*UserInfo, error) {
+func (basic *Basic) Decrypt(sessionKey, encryptedData, iv string) (*UserInfo, error) {
 	cipherText, err := getCipherText(sessionKey, encryptedData, iv)
 	if err != nil {
 		return nil, err
@@ -106,14 +106,14 @@ func (wxa *MiniProgram) Decrypt(sessionKey, encryptedData, iv string) (*UserInfo
 	if err != nil {
 		return nil, err
 	}
-	if userInfo.Watermark.AppID != wxa.AppID {
+	if userInfo.Watermark.AppID != basic.AppID {
 		return nil, ErrAppIDNotMatch
 	}
 	return &userInfo, nil
 }
 
 // DecryptPhone 解密数据(手机)
-func (wxa *MiniProgram) DecryptPhone(sessionKey, encryptedData, iv string) (*PhoneInfo, error) {
+func (basic *Basic) DecryptPhone(sessionKey, encryptedData, iv string) (*PhoneInfo, error) {
 	cipherText, err := getCipherText(sessionKey, encryptedData, iv)
 	if err != nil {
 		return nil, err
@@ -123,7 +123,7 @@ func (wxa *MiniProgram) DecryptPhone(sessionKey, encryptedData, iv string) (*Pho
 	if err != nil {
 		return nil, err
 	}
-	if phoneInfo.Watermark.AppID != wxa.AppID {
+	if phoneInfo.Watermark.AppID != basic.AppID {
 		return nil, ErrAppIDNotMatch
 	}
 	return &phoneInfo, nil

+ 12 - 0
miniprogram/config/config.go

@@ -0,0 +1,12 @@
+package config
+
+import (
+	"github.com/silenceper/wechat/cache"
+)
+
+//Config config for 小程序
+type Config struct {
+	AppID     string `json:"app_id"`     //appid
+	AppSecret string `json:"app_secret"` //appsecret
+	Cache     cache.Cache
+}

+ 4 - 2
context/access_token.go

@@ -12,6 +12,8 @@ import (
 const (
 	//AccessTokenURL 获取access_token的接口
 	AccessTokenURL = "https://api.weixin.qq.com/cgi-bin/token"
+	//CacheKeyPrefix cache前缀
+	CacheKeyPrefix = "gowechat_miniprogram_"
 )
 
 //ResAccessToken struct
@@ -43,7 +45,7 @@ func (ctx *Context) GetAccessToken() (accessToken string, err error) {
 	if ctx.accessTokenFunc != nil {
 		return ctx.accessTokenFunc(ctx)
 	}
-	accessTokenCacheKey := fmt.Sprintf("access_token_%s", ctx.AppID)
+	accessTokenCacheKey := fmt.Sprintf("%s_access_token_%s", CacheKeyPrefix, ctx.AppID)
 	val := ctx.Cache.Get(accessTokenCacheKey)
 	if val != nil {
 		accessToken = val.(string)
@@ -78,7 +80,7 @@ func (ctx *Context) GetAccessTokenFromServer() (resAccessToken ResAccessToken, e
 		return
 	}
 
-	accessTokenCacheKey := fmt.Sprintf("access_token_%s", ctx.AppID)
+	accessTokenCacheKey := fmt.Sprintf("%s_access_token_%s", CacheKeyPrefix, ctx.AppID)
 	expires := resAccessToken.ExpiresIn - 1500
 	err = ctx.Cache.Set(accessTokenCacheKey, resAccessToken.AccessToken, time.Duration(expires)*time.Second)
 	return

+ 18 - 0
miniprogram/context/context.go

@@ -0,0 +1,18 @@
+package context
+
+import (
+	"sync"
+
+	"github.com/silenceper/wechat/miniprogram/config"
+)
+
+// Context struct
+type Context struct {
+	*config.Config
+
+	//accessTokenLock 读写锁 同一个AppID一个
+	accessTokenLock *sync.RWMutex
+
+	//accessTokenFunc 自定义获取 access token 的方法
+	accessTokenFunc GetAccessTokenFunc
+}

+ 51 - 8
miniprogram/miniprogram.go

@@ -1,17 +1,60 @@
 package miniprogram
 
 import (
-	"github.com/silenceper/wechat/context"
+	"sync"
+
+	"github.com/silenceper/wechat/miniprogram/analysis"
+	"github.com/silenceper/wechat/miniprogram/auth"
+	"github.com/silenceper/wechat/miniprogram/basic"
+	"github.com/silenceper/wechat/miniprogram/config"
+	"github.com/silenceper/wechat/miniprogram/context"
+	"github.com/silenceper/wechat/miniprogram/qrcode"
+	"github.com/silenceper/wechat/miniprogram/tcb"
 )
 
-// MiniProgram struct extends context
+//MiniProgram 微信小程序相关API
 type MiniProgram struct {
-	*context.Context
+	ctx *context.Context
+}
+
+//NewMiniProgram 实例化小程序API
+func NewMiniProgram(cfg *config.Config) *MiniProgram {
+	if cfg.Cache == nil {
+		panic("cache未设置")
+	}
+	ctx := &context.Context{
+		Config: cfg,
+	}
+	ctx.SetAccessTokenLock(new(sync.RWMutex))
+	return &MiniProgram{ctx}
+}
+
+// GetContext get Context
+func (miniProgram *MiniProgram) GetContext() *context.Context {
+	return miniProgram.ctx
+}
+
+// GetBasic  基础接口(小程序加解密)
+func (miniProgram *MiniProgram) GetBasic() *basic.Basic {
+	return basic.NewBasic(miniProgram.ctx)
+}
+
+//GetAuth 登录/用户信息相关接口
+func (miniProgram *MiniProgram) GetAuth() *auth.Auth {
+	return auth.NewAuth(miniProgram.ctx)
+}
+
+//GetAnalysis 数据分析
+func (miniProgram *MiniProgram) GetAnalysis() *analysis.Analysis {
+	return analysis.NewAnalysis(miniProgram.ctx)
+}
+
+//GetQRCode 小程序码相关API
+func (miniProgram *MiniProgram) GetQRCode() *qrcode.QRCode {
+	return qrcode.NewQRCode(miniProgram.ctx)
 }
 
-// NewMiniProgram 实例化小程序接口
-func NewMiniProgram(context *context.Context) *MiniProgram {
-	miniProgram := new(MiniProgram)
-	miniProgram.Context = context
-	return miniProgram
+//GetTcb 小程序云开发API
+func (miniProgram *MiniProgram) GetTcb() *tcb.Tcb {
+	return tcb.NewTcb(miniProgram.ctx)
 }

+ 29 - 16
miniprogram/qrcode.go

@@ -1,10 +1,11 @@
-package miniprogram
+package qrcode
 
 import (
 	"encoding/json"
 	"fmt"
 	"strings"
 
+	"github.com/silenceper/wechat/miniprogram/context"
 	"github.com/silenceper/wechat/util"
 )
 
@@ -14,6 +15,25 @@ const (
 	getWXACodeUnlimitURL = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s"
 )
 
+//QRCode struct
+type QRCode struct {
+	*context.Context
+}
+
+//NewQRCode 实例
+func NewQRCode(context *context.Context) *QRCode {
+	qrCode := new(QRCode)
+	qrCode.Context = context
+	return qrCode
+}
+
+// Color QRCode color
+type Color struct {
+	R string `json:"r"`
+	G string `json:"g"`
+	B string `json:"b"`
+}
+
 // QRCoder 小程序码参数
 type QRCoder struct {
 	// page 必须是已经发布的小程序存在的页面,根路径前不要填加 /,不能携带参数(参数请放在scene字段里),如果不填写这个字段,默认跳主页面
@@ -32,17 +52,10 @@ type QRCoder struct {
 	IsHyaline bool `json:"is_hyaline,omitempty"`
 }
 
-// Color QRCode color
-type Color struct {
-	R string `json:"r"`
-	G string `json:"g"`
-	B string `json:"b"`
-}
-
 // fetchCode 请求并返回二维码二进制数据
-func (wxa *MiniProgram) fetchCode(urlStr string, body interface{}) (response []byte, err error) {
+func (qrCode *QRCode) fetchCode(urlStr string, body interface{}) (response []byte, err error) {
 	var accessToken string
-	accessToken, err = wxa.GetAccessToken()
+	accessToken, err = qrCode.GetAccessToken()
 	if err != nil {
 		return
 	}
@@ -74,18 +87,18 @@ func (wxa *MiniProgram) fetchCode(urlStr string, body interface{}) (response []b
 
 // CreateWXAQRCode 获取小程序二维码,适用于需要的码数量较少的业务场景
 // 文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/createWXAQRCode.html
-func (wxa *MiniProgram) CreateWXAQRCode(coderParams QRCoder) (response []byte, err error) {
-	return wxa.fetchCode(createWXAQRCodeURL, coderParams)
+func (qrCode *QRCode) CreateWXAQRCode(coderParams QRCoder) (response []byte, err error) {
+	return qrCode.fetchCode(createWXAQRCodeURL, coderParams)
 }
 
 // GetWXACode 获取小程序码,适用于需要的码数量较少的业务场景
 // 文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/getWXACode.html
-func (wxa *MiniProgram) GetWXACode(coderParams QRCoder) (response []byte, err error) {
-	return wxa.fetchCode(getWXACodeURL, coderParams)
+func (qrCode *QRCode) GetWXACode(coderParams QRCoder) (response []byte, err error) {
+	return qrCode.fetchCode(getWXACodeURL, coderParams)
 }
 
 // GetWXACodeUnlimit 获取小程序码,适用于需要的码数量极多的业务场景
 // 文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/getWXACodeUnlimit.html
-func (wxa *MiniProgram) GetWXACodeUnlimit(coderParams QRCoder) (response []byte, err error) {
-	return wxa.fetchCode(getWXACodeUnlimitURL, coderParams)
+func (qrCode *QRCode) GetWXACodeUnlimit(coderParams QRCoder) (response []byte, err error) {
+	return qrCode.fetchCode(getWXACodeUnlimitURL, coderParams)
 }

tcb/README.md → miniprogram/tcb/README.md


tcb/cloudfunction.go → miniprogram/tcb/cloudfunction.go


tcb/database.go → miniprogram/tcb/database.go


tcb/file.go → miniprogram/tcb/file.go


+ 3 - 4
tcb/tcb.go

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

+ 0 - 95
oauth/qy_oauth.go

@@ -1,95 +0,0 @@
-package oauth
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/url"
-
-	"github.com/silenceper/wechat/util"
-)
-
-var (
-	qyRedirectOauthURL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&agentid=%s&state=%s#wechat_redirect"
-	qyUserInfoURL      = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=%s&code=%s"
-	qyUserDetailURL    = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserdetail"
-)
-
-//GetQyRedirectURL 获取企业微信跳转的url地址
-func (oauth *Oauth) GetQyRedirectURL(redirectURI, agentid, scope, state string) (string, error) {
-	//url encode
-	urlStr := url.QueryEscape(redirectURI)
-	return fmt.Sprintf(qyRedirectOauthURL, oauth.AppID, urlStr, scope, agentid, state), nil
-}
-
-//QyUserInfo 用户授权获取到用户信息
-type QyUserInfo struct {
-	util.CommonError
-
-	UserID     string `json:"UserId"`
-	DeviceID   string `json:"DeviceId"`
-	UserTicket string `json:"user_ticket"`
-	ExpiresIn  int64  `json:"expires_in"`
-}
-
-//GetQyUserInfoByCode 根据code获取企业user_info
-func (oauth *Oauth) GetQyUserInfoByCode(code string) (result QyUserInfo, err error) {
-	qyAccessToken, e := oauth.GetQyAccessToken()
-	if e != nil {
-		err = e
-		return
-	}
-	urlStr := fmt.Sprintf(qyUserInfoURL, qyAccessToken, code)
-	var response []byte
-	response, err = util.HTTPGet(urlStr)
-	if err != nil {
-		return
-	}
-	err = json.Unmarshal(response, &result)
-	if err != nil {
-		return
-	}
-	if result.ErrCode != 0 {
-		err = fmt.Errorf("GetQyUserInfoByCode error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
-		return
-	}
-	return
-}
-
-//QyUserDetail 到用户详情
-type QyUserDetail struct {
-	util.CommonError
-
-	UserID string `json:"UserId"`
-	Name   string `json:"name"`
-	Mobile string `json:"mobile"`
-	Gender string `json:"gender"`
-	Email  string `json:"email"`
-	Avatar string `json:"avatar"`
-	QrCode string `json:"qr_code"`
-}
-
-//GetQyUserDetailUserTicket 根据user_ticket获取到用户详情
-func (oauth *Oauth) GetQyUserDetailUserTicket(userTicket string) (result QyUserDetail, err error) {
-	var qyAccessToken string
-	qyAccessToken, err = oauth.GetQyAccessToken()
-	if err != nil {
-		return
-	}
-	uri := fmt.Sprintf("%s?access_token=%s", qyUserDetailURL, qyAccessToken)
-	var response []byte
-	response, err = util.PostJSON(uri, map[string]string{
-		"user_ticket": userTicket,
-	})
-	if err != nil {
-		return
-	}
-	err = json.Unmarshal(response, &result)
-	if err != nil {
-		return
-	}
-	if result.ErrCode != 0 {
-		err = fmt.Errorf("GetQyUserDetailUserTicket Error , errcode=%d , errmsg=%s", result.ErrCode, result.ErrMsg)
-		return
-	}
-	return
-}

+ 5 - 0
officialaccount/README.md

@@ -0,0 +1,5 @@
+# 微信公众号
+
+[官方文档](https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Overview.html)
+
+## 快速入门

+ 15 - 0
officialaccount/basic/basic.go

@@ -0,0 +1,15 @@
+package basic
+
+import "github.com/silenceper/wechat/officialaccount/context"
+
+//Basic struct
+type Basic struct {
+	*context.Context
+}
+
+//NewBasic 实例
+func NewBasic(context *context.Context) *Basic {
+	basic := new(Basic)
+	basic.Context = context
+	return basic
+}

+ 3 - 16
qr/qr.go

@@ -1,4 +1,4 @@
-package qr
+package basic
 
 import (
 	"encoding/json"
@@ -6,7 +6,6 @@ import (
 	"reflect"
 	"time"
 
-	"github.com/silenceper/wechat/context"
 	"github.com/silenceper/wechat/util"
 )
 
@@ -23,18 +22,6 @@ const (
 	actionLimitStr = "QR_LIMIT_STR_SCENE"
 )
 
-// QR 二维码
-type QR struct {
-	*context.Context
-}
-
-//NewQR 二维码实例
-func NewQR(context *context.Context) *QR {
-	q := new(QR)
-	q.Context = context
-	return q
-}
-
 // Request 临时二维码
 type Request struct {
 	ExpireSeconds int64  `json:"expire_seconds,omitempty"`
@@ -56,8 +43,8 @@ type Ticket struct {
 }
 
 // GetQRTicket 获取二维码 Ticket
-func (q *QR) GetQRTicket(tq *Request) (t *Ticket, err error) {
-	accessToken, err := q.GetAccessToken()
+func (basic *Basic) GetQRTicket(tq *Request) (t *Ticket, err error) {
+	accessToken, err := basic.GetAccessToken()
 	if err != nil {
 		return
 	}

+ 14 - 0
officialaccount/config/config.go

@@ -0,0 +1,14 @@
+package config
+
+import (
+	"github.com/silenceper/wechat/cache"
+)
+
+//Config config for 微信公众号
+type Config struct {
+	AppID          string `json:"app_id"`           //appid
+	AppSecret      string `json:"app_secret"`       //appsecret
+	Token          string `json:"token"`            //token
+	EncodingAESKey string `json:"encoding_aes_key"` //EncodingAESKey
+	Cache          cache.Cache
+}

+ 87 - 0
officialaccount/context/access_token.go

@@ -0,0 +1,87 @@
+package context
+
+import (
+	"encoding/json"
+	"fmt"
+	"sync"
+	"time"
+
+	"github.com/silenceper/wechat/util"
+)
+
+const (
+	//AccessTokenURL 获取access_token的接口
+	AccessTokenURL = "https://api.weixin.qq.com/cgi-bin/token"
+	//CacheKeyPrefix 微信公众号cache key前缀
+	CacheKeyPrefix = "gowechat_officialaccount_"
+)
+
+//ResAccessToken struct
+type ResAccessToken struct {
+	util.CommonError
+
+	AccessToken string `json:"access_token"`
+	ExpiresIn   int64  `json:"expires_in"`
+}
+
+//GetAccessTokenFunc 获取 access token 的函数签名
+type GetAccessTokenFunc func(ctx *Context) (accessToken string, err error)
+
+//SetAccessTokenLock 设置读写锁(一个appID一个读写锁)
+func (ctx *Context) SetAccessTokenLock(l *sync.RWMutex) {
+	ctx.accessTokenLock = l
+}
+
+//SetGetAccessTokenFunc 设置自定义获取accessToken的方式, 需要自己实现缓存
+func (ctx *Context) SetGetAccessTokenFunc(f GetAccessTokenFunc) {
+	ctx.accessTokenFunc = f
+}
+
+//GetAccessToken 获取access_token
+func (ctx *Context) GetAccessToken() (accessToken string, err error) {
+	ctx.accessTokenLock.Lock()
+	defer ctx.accessTokenLock.Unlock()
+
+	if ctx.accessTokenFunc != nil {
+		return ctx.accessTokenFunc(ctx)
+	}
+	accessTokenCacheKey := fmt.Sprintf("%s_access_token_%s", CacheKeyPrefix, ctx.AppID)
+	val := ctx.Cache.Get(accessTokenCacheKey)
+	if val != nil {
+		accessToken = val.(string)
+		return
+	}
+
+	//从微信服务器获取
+	var resAccessToken ResAccessToken
+	resAccessToken, err = ctx.GetAccessTokenFromServer()
+	if err != nil {
+		return
+	}
+
+	accessToken = resAccessToken.AccessToken
+	return
+}
+
+//GetAccessTokenFromServer 强制从微信服务器获取token
+func (ctx *Context) GetAccessTokenFromServer() (resAccessToken ResAccessToken, err error) {
+	url := fmt.Sprintf("%s?grant_type=client_credential&appid=%s&secret=%s", AccessTokenURL, ctx.AppID, ctx.AppSecret)
+	var body []byte
+	body, err = util.HTTPGet(url)
+	if err != nil {
+		return
+	}
+	err = json.Unmarshal(body, &resAccessToken)
+	if err != nil {
+		return
+	}
+	if resAccessToken.ErrMsg != "" {
+		err = fmt.Errorf("get access_token error : errcode=%v , errormsg=%v", resAccessToken.ErrCode, resAccessToken.ErrMsg)
+		return
+	}
+
+	accessTokenCacheKey := fmt.Sprintf("%s_access_token_%s", CacheKeyPrefix, ctx.AppID)
+	expires := resAccessToken.ExpiresIn - 1500
+	err = ctx.Cache.Set(accessTokenCacheKey, resAccessToken.AccessToken, time.Duration(expires)*time.Second)
+	return
+}

+ 31 - 0
officialaccount/context/context.go

@@ -0,0 +1,31 @@
+package context
+
+import (
+	"sync"
+
+	"github.com/silenceper/wechat/officialaccount/config"
+)
+
+// Context struct
+type Context struct {
+	*config.Config
+
+	//accessTokenLock 读写锁 同一个AppID一个
+	accessTokenLock *sync.RWMutex
+
+	//jsAPITicket 读写锁 同一个AppID一个
+	jsAPITicketLock *sync.RWMutex
+
+	//accessTokenFunc 自定义获取 access token 的方法
+	accessTokenFunc GetAccessTokenFunc
+}
+
+// SetJsAPITicketLock 设置jsAPITicket的lock
+func (ctx *Context) SetJsAPITicketLock(lock *sync.RWMutex) {
+	ctx.jsAPITicketLock = lock
+}
+
+// GetJsAPITicketLock 获取jsAPITicket 的lock
+func (ctx *Context) GetJsAPITicketLock() *sync.RWMutex {
+	return ctx.jsAPITicketLock
+}

device/authorize.go → officialaccount/device/authorize.go


device/bind.go → officialaccount/device/bind.go


+ 1 - 1
device/device.go

@@ -4,7 +4,7 @@ import (
 	"encoding/json"
 	"fmt"
 
-	"github.com/silenceper/wechat/context"
+	"github.com/silenceper/wechat/officialaccount/context"
 	"github.com/silenceper/wechat/util"
 )
 

device/message.go → officialaccount/device/message.go


device/qrcode.go → officialaccount/device/qrcode.go


+ 3 - 3
js/js.go

@@ -5,7 +5,7 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/silenceper/wechat/context"
+	"github.com/silenceper/wechat/officialaccount/context"
 	"github.com/silenceper/wechat/util"
 )
 
@@ -67,7 +67,7 @@ func (js *Js) GetTicket() (ticketStr string, err error) {
 	defer js.GetJsAPITicketLock().Unlock()
 
 	//先从cache中取
-	jsAPITicketCacheKey := fmt.Sprintf("jsapi_ticket_%s", js.AppID)
+	jsAPITicketCacheKey := fmt.Sprintf("%s_jsapi_ticket_%s", context.CacheKeyPrefix, js.AppID)
 	val := js.Cache.Get(jsAPITicketCacheKey)
 	if val != nil {
 		ticketStr = val.(string)
@@ -102,7 +102,7 @@ func (js *Js) getTicketFromServer() (ticket resTicket, err error) {
 		return
 	}
 
-	jsAPITicketCacheKey := fmt.Sprintf("jsapi_ticket_%s", js.AppID)
+	jsAPITicketCacheKey := fmt.Sprintf("%s_jsapi_ticket_%s", context.CacheKeyPrefix, js.AppID)
 	expires := ticket.ExpiresIn - 1500
 	err = js.Cache.Set(jsAPITicketCacheKey, ticket.Ticket, time.Duration(expires)*time.Second)
 	return

+ 1 - 1
material/material.go

@@ -5,7 +5,7 @@ import (
 	"errors"
 	"fmt"
 
-	"github.com/silenceper/wechat/context"
+	"github.com/silenceper/wechat/officialaccount/context"
 	"github.com/silenceper/wechat/util"
 )
 

material/media.go → officialaccount/material/media.go


menu/button.go → officialaccount/menu/button.go


+ 1 - 1
menu/menu.go

@@ -4,7 +4,7 @@ import (
 	"encoding/json"
 	"fmt"
 
-	"github.com/silenceper/wechat/context"
+	"github.com/silenceper/wechat/officialaccount/context"
 	"github.com/silenceper/wechat/util"
 )
 

+ 2 - 1
message/customer_message.go

@@ -3,7 +3,8 @@ package message
 import (
 	"encoding/json"
 	"fmt"
-	"github.com/silenceper/wechat/context"
+
+	"github.com/silenceper/wechat/officialaccount/context"
 	"github.com/silenceper/wechat/util"
 )
 

message/image.go → officialaccount/message/image.go


+ 1 - 1
message/message.go

@@ -3,7 +3,7 @@ package message
 import (
 	"encoding/xml"
 
-	"github.com/silenceper/wechat/device"
+	"github.com/silenceper/wechat/officialaccount/device"
 )
 
 // MsgType 基本消息类型

message/music.go → officialaccount/message/music.go


message/news.go → officialaccount/message/news.go


message/ransfer_customer.go → officialaccount/message/ransfer_customer.go


message/reply.go → officialaccount/message/reply.go


+ 1 - 1
message/template.go

@@ -4,7 +4,7 @@ import (
 	"encoding/json"
 	"fmt"
 
-	"github.com/silenceper/wechat/context"
+	"github.com/silenceper/wechat/officialaccount/context"
 	"github.com/silenceper/wechat/util"
 )
 

message/text.go → officialaccount/message/text.go


message/video.go → officialaccount/message/video.go


message/voice.go → officialaccount/message/voice.go


+ 1 - 1
oauth/oauth.go

@@ -6,7 +6,7 @@ import (
 	"net/http"
 	"net/url"
 
-	"github.com/silenceper/wechat/context"
+	"github.com/silenceper/wechat/officialaccount/context"
 	"github.com/silenceper/wechat/util"
 )
 

+ 94 - 0
officialaccount/officialaccount.go

@@ -0,0 +1,94 @@
+package officialaccount
+
+import (
+	"net/http"
+	"sync"
+
+	"github.com/silenceper/wechat/officialaccount/basic"
+	"github.com/silenceper/wechat/officialaccount/config"
+	"github.com/silenceper/wechat/officialaccount/context"
+	"github.com/silenceper/wechat/officialaccount/device"
+	"github.com/silenceper/wechat/officialaccount/js"
+	"github.com/silenceper/wechat/officialaccount/material"
+	"github.com/silenceper/wechat/officialaccount/menu"
+	"github.com/silenceper/wechat/officialaccount/message"
+	"github.com/silenceper/wechat/officialaccount/oauth"
+	"github.com/silenceper/wechat/officialaccount/server"
+	"github.com/silenceper/wechat/officialaccount/user"
+)
+
+//OfficialAccount 微信公众号相关API
+type OfficialAccount struct {
+	ctx *context.Context
+}
+
+//NewOfficialAccount 实例化公众号API
+func NewOfficialAccount(cfg *config.Config) *OfficialAccount {
+	if cfg.Cache == nil {
+		panic("cache未设置")
+	}
+	ctx := &context.Context{
+		Config: cfg,
+	}
+	ctx.SetAccessTokenLock(new(sync.RWMutex))
+	ctx.SetJsAPITicketLock(new(sync.RWMutex))
+	return &OfficialAccount{ctx}
+}
+
+// GetContext get Context
+func (officialAccount *OfficialAccount) GetContext() *context.Context {
+	return officialAccount.ctx
+}
+
+// GetBasic qr/url 相关配置
+func (officialAccount *OfficialAccount) GetBasic() *basic.Basic {
+	return basic.NewBasic(officialAccount.ctx)
+}
+
+// GetMenu 菜单管理接口
+func (officialAccount *OfficialAccount) GetMenu() *menu.Menu {
+	return menu.NewMenu(officialAccount.ctx)
+}
+
+// GetServer 消息管理
+func (officialAccount *OfficialAccount) GetServer(req *http.Request, writer http.ResponseWriter) *server.Server {
+	srv := server.NewServer(officialAccount.ctx)
+	srv.Request = req
+	srv.Writer = writer
+	return srv
+}
+
+//GetAccessToken 获取access_token
+func (officialAccount *OfficialAccount) GetAccessToken() (string, error) {
+	return officialAccount.ctx.GetAccessToken()
+}
+
+// GetOauth oauth2网页授权
+func (officialAccount *OfficialAccount) GetOauth() *oauth.Oauth {
+	return oauth.NewOauth(officialAccount.ctx)
+}
+
+// GetMaterial 素材管理
+func (officialAccount *OfficialAccount) GetMaterial() *material.Material {
+	return material.NewMaterial(officialAccount.ctx)
+}
+
+// GetJs js-sdk配置
+func (officialAccount *OfficialAccount) GetJs() *js.Js {
+	return js.NewJs(officialAccount.ctx)
+}
+
+// GetUser 用户管理接口
+func (officialAccount *OfficialAccount) GetUser() *user.User {
+	return user.NewUser(officialAccount.ctx)
+}
+
+// GetTemplate 模板消息接口
+func (officialAccount *OfficialAccount) GetTemplate() *message.Template {
+	return message.NewTemplate(officialAccount.ctx)
+}
+
+// GetDevice 获取智能设备的实例
+func (officialAccount *OfficialAccount) GetDevice() *device.Device {
+	return device.NewDevice(officialAccount.ctx)
+}

+ 6 - 2
server/server.go

@@ -5,18 +5,22 @@ import (
 	"errors"
 	"fmt"
 	"io/ioutil"
+	"net/http"
 	"reflect"
 	"runtime/debug"
 	"strconv"
 
-	"github.com/silenceper/wechat/context"
-	"github.com/silenceper/wechat/message"
+	"github.com/silenceper/wechat/officialaccount/context"
+	"github.com/silenceper/wechat/officialaccount/message"
+
 	"github.com/silenceper/wechat/util"
 )
 
 //Server struct
 type Server struct {
 	*context.Context
+	Writer  http.ResponseWriter
+	Request *http.Request
 
 	debug bool
 

+ 58 - 0
officialaccount/server/util.go

@@ -0,0 +1,58 @@
+package server
+
+import (
+	"encoding/xml"
+	"net/http"
+)
+
+var xmlContentType = []string{"application/xml; charset=utf-8"}
+var plainContentType = []string{"text/plain; charset=utf-8"}
+
+func writeContextType(w http.ResponseWriter, value []string) {
+	header := w.Header()
+	if val := header["Content-Type"]; len(val) == 0 {
+		header["Content-Type"] = value
+	}
+}
+
+//Render render from bytes
+func (srv *Server) Render(bytes []byte) {
+	//debug
+	//fmt.Println("response msg = ", string(bytes))
+	srv.Writer.WriteHeader(200)
+	_, err := srv.Writer.Write(bytes)
+	if err != nil {
+		panic(err)
+	}
+}
+
+//String render from string
+func (srv *Server) String(str string) {
+	writeContextType(srv.Writer, plainContentType)
+	srv.Render([]byte(str))
+}
+
+//XML render to xml
+func (srv *Server) XML(obj interface{}) {
+	writeContextType(srv.Writer, xmlContentType)
+	bytes, err := xml.Marshal(obj)
+	if err != nil {
+		panic(err)
+	}
+	srv.Render(bytes)
+}
+
+// Query returns the keyed url query value if it exists
+func (srv *Server) Query(key string) string {
+	value, _ := srv.GetQuery(key)
+	return value
+}
+
+// GetQuery is like Query(), it returns the keyed url query value
+func (srv *Server) GetQuery(key string) (string, bool) {
+	req := srv.Request
+	if values, ok := req.URL.Query()[key]; ok && len(values) > 0 {
+		return values[0], true
+	}
+	return "", false
+}

+ 1 - 1
user/user.go

@@ -5,7 +5,7 @@ import (
 	"fmt"
 	"net/url"
 
-	"github.com/silenceper/wechat/context"
+	"github.com/silenceper/wechat/officialaccount/context"
 	"github.com/silenceper/wechat/util"
 )
 

+ 5 - 0
openplatform/README.md

@@ -0,0 +1,5 @@
+# 微信开放平台
+
+[官方文档](https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Third_party_platform_appid.html)
+
+## 快速入门

+ 5 - 0
pay/README.md

@@ -0,0 +1,5 @@
+# 微信支付
+
+[官方文档](https://pay.weixin.qq.com/wiki/doc/api/index.html)
+
+## 快速入门

+ 9 - 0
pay/config/config.go

@@ -0,0 +1,9 @@
+package config
+
+//Config config for pay
+type Config struct {
+	AppID     string `json:"app_id"`
+	MchID     string `json:"mch_id"`
+	Key       string `json:"key"`
+	NotifyURL string `json:"notify_url"`
+}

+ 15 - 0
pay/notify/notify.go

@@ -0,0 +1,15 @@
+package notify
+
+import (
+	"github.com/silenceper/wechat/pay/config"
+)
+
+//Notify 回调
+type Notify struct {
+	*config.Config
+}
+
+//NewNotify new
+func NewNotify(cfg *config.Config) *Notify {
+	return &Notify{cfg}
+}

+ 10 - 9
pay/notify_result.go

@@ -1,9 +1,10 @@
-package pay
+package notify
 
 import (
 	"fmt"
-	"github.com/silenceper/wechat/util"
 	"sort"
+
+	"github.com/silenceper/wechat/util"
 )
 
 // Base 公用参数
@@ -14,8 +15,8 @@ type Base struct {
 	Sign     string `xml:"sign"`
 }
 
-// NotifyResult 下单回调
-type NotifyResult struct {
+// PaidResult 下单回调
+type PaidResult struct {
 	Base
 	ReturnCode    string `xml:"return_code"`
 	ReturnMsg     string `xml:"return_msg"`
@@ -34,14 +35,14 @@ type NotifyResult struct {
 	TimeEnd       string `xml:"time_end"`
 }
 
-// NotifyResp 消息通知返回
-type NotifyResp struct {
+// PaidResp 消息通知返回
+type PaidResp struct {
 	ReturnCode string `xml:"return_code"`
 	ReturnMsg  string `xml:"return_msg"`
 }
 
-// VerifySign 验签
-func (pcf *Pay) VerifySign(notifyRes NotifyResult) bool {
+// PaidVerifySign 支付成功结果验签
+func (notify *Notify) PaidVerifySign(notifyRes PaidResult) bool {
 	// 封装map 请求过来的 map
 	resMap := make(map[string]interface{})
 	// base
@@ -77,7 +78,7 @@ func (pcf *Pay) VerifySign(notifyRes NotifyResult) bool {
 		}
 	}
 	// STEP3, 在键值对的最后加上key=API_KEY
-	signStrings = signStrings + "key=" + pcf.PayKey
+	signStrings = signStrings + "key=" + notify.Key
 	// STEP4, 进行MD5签名并且将所有字符转为大写.
 	sign := util.MD5Sum(signStrings)
 	if sign != notifyRes.Sign {

+ 220 - 0
pay/order/pay.go

@@ -0,0 +1,220 @@
+package order
+
+import (
+	"crypto/hmac"
+	"crypto/md5"
+	"crypto/sha256"
+	"encoding/hex"
+	"encoding/xml"
+	"errors"
+	"hash"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/silenceper/wechat/pay/config"
+	"github.com/silenceper/wechat/util"
+)
+
+//https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
+var payGateway = "https://api.mch.weixin.qq.com/pay/unifiedorder"
+
+// Order struct extends context
+type Order struct {
+	*config.Config
+}
+
+// NewOrder return an instance of order package
+func NewOrder(cfg *config.Config) *Order {
+	order := Order{cfg}
+	return &order
+}
+
+// Params was NEEDED when request unifiedorder
+// 传入的参数,用于生成 prepay_id 的必需参数
+type Params struct {
+	TotalFee   string
+	CreateIP   string
+	Body       string
+	OutTradeNo string
+	OpenID     string
+	TradeType  string
+	SignType   string
+	Detail     string
+	Attach     string
+	GoodsTag   string
+	NotifyURL  string
+}
+
+// Config 是传出用于 js sdk 用的参数
+type Config struct {
+	Timestamp string `json:"timestamp"`
+	NonceStr  string `json:"nonceStr"`
+	PrePayID  string `json:"prePayId"`
+	SignType  string `json:"signType"`
+	Package   string `json:"package"`
+	PaySign   string `json:"paySign"`
+}
+
+// PreOrder 是 unifie order 接口的返回
+type PreOrder struct {
+	ReturnCode string `xml:"return_code"`
+	ReturnMsg  string `xml:"return_msg"`
+	AppID      string `xml:"appid,omitempty"`
+	MchID      string `xml:"mch_id,omitempty"`
+	NonceStr   string `xml:"nonce_str,omitempty"`
+	Sign       string `xml:"sign,omitempty"`
+	ResultCode string `xml:"result_code,omitempty"`
+	TradeType  string `xml:"trade_type,omitempty"`
+	PrePayID   string `xml:"prepay_id,omitempty"`
+	CodeURL    string `xml:"code_url,omitempty"`
+	ErrCode    string `xml:"err_code,omitempty"`
+	ErrCodeDes string `xml:"err_code_des,omitempty"`
+}
+
+// payRequest 接口请求参数
+type payRequest struct {
+	AppID          string `xml:"appid"`
+	MchID          string `xml:"mch_id"`
+	DeviceInfo     string `xml:"device_info,omitempty"`
+	NonceStr       string `xml:"nonce_str"`
+	Sign           string `xml:"sign"`
+	SignType       string `xml:"sign_type,omitempty"`
+	Body           string `xml:"body"`
+	Detail         string `xml:"detail,omitempty"`
+	Attach         string `xml:"attach,omitempty"`      // 附加数据
+	OutTradeNo     string `xml:"out_trade_no"`          // 商户订单号
+	FeeType        string `xml:"fee_type,omitempty"`    // 标价币种
+	TotalFee       string `xml:"total_fee"`             // 标价金额
+	SpbillCreateIP string `xml:"spbill_create_ip"`      // 终端IP
+	TimeStart      string `xml:"time_start,omitempty"`  // 交易起始时间
+	TimeExpire     string `xml:"time_expire,omitempty"` // 交易结束时间
+	GoodsTag       string `xml:"goods_tag,omitempty"`   // 订单优惠标记
+	NotifyURL      string `xml:"notify_url"`            // 通知地址
+	TradeType      string `xml:"trade_type"`            // 交易类型
+	ProductID      string `xml:"product_id,omitempty"`  // 商品ID
+	LimitPay       string `xml:"limit_pay,omitempty"`   //
+	OpenID         string `xml:"openid,omitempty"`      // 用户标识
+	SceneInfo      string `xml:"scene_info,omitempty"`  // 场景信息
+}
+
+// BridgeConfig get js bridge config
+func (o *Order) BridgeConfig(p *Params) (cfg Config, err error) {
+	var (
+		buffer    strings.Builder
+		h         hash.Hash
+		timestamp = strconv.FormatInt(time.Now().Unix(), 10)
+	)
+	order, err := o.PrePayOrder(p)
+	if err != nil {
+		return
+	}
+	buffer.WriteString("appId=")
+	buffer.WriteString(order.AppID)
+	buffer.WriteString("&nonceStr=")
+	buffer.WriteString(order.NonceStr)
+	buffer.WriteString("&package=")
+	buffer.WriteString("prepay_id=" + order.PrePayID)
+	buffer.WriteString("&signType=")
+	buffer.WriteString(p.SignType)
+	buffer.WriteString("&timeStamp=")
+	buffer.WriteString(timestamp)
+	buffer.WriteString("&key=")
+	buffer.WriteString(o.Key)
+	if p.SignType == "MD5" {
+		h = md5.New()
+	} else {
+		h = hmac.New(sha256.New, []byte(o.Key))
+	}
+	h.Write([]byte(buffer.String()))
+	// 签名
+	cfg.PaySign = strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
+	cfg.NonceStr = order.NonceStr
+	cfg.Timestamp = timestamp
+	cfg.PrePayID = order.PrePayID
+	cfg.SignType = p.SignType
+	cfg.Package = "prepay_id=" + order.PrePayID
+	return
+}
+
+// PrePayOrder return data for invoke wechat payment
+func (o *Order) PrePayOrder(p *Params) (payOrder PreOrder, err error) {
+	nonceStr := util.RandomStr(32)
+	notifyURL := o.NotifyURL
+	// 签名类型
+	if p.SignType == "" {
+		p.SignType = "MD5"
+	}
+	// 通知地址
+	if p.NotifyURL != "" {
+		notifyURL = p.NotifyURL
+	}
+	param := make(map[string]interface{})
+	param["appid"] = o.AppID
+	param["body"] = p.Body
+	param["mch_id"] = o.MchID
+	param["nonce_str"] = nonceStr
+	param["out_trade_no"] = p.OutTradeNo
+	param["spbill_create_ip"] = p.CreateIP
+	param["total_fee"] = p.TotalFee
+	param["trade_type"] = p.TradeType
+	param["openid"] = p.OpenID
+	param["sign_type"] = p.SignType
+	param["detail"] = p.Detail
+	param["attach"] = p.Attach
+	param["goods_tag"] = p.GoodsTag
+	param["notify_url"] = notifyURL
+
+	bizKey := "&key=" + o.Key
+	str := util.OrderParam(param, bizKey)
+	sign := util.MD5Sum(str)
+	request := payRequest{
+		AppID:          o.AppID,
+		MchID:          o.MchID,
+		NonceStr:       nonceStr,
+		Sign:           sign,
+		Body:           p.Body,
+		OutTradeNo:     p.OutTradeNo,
+		TotalFee:       p.TotalFee,
+		SpbillCreateIP: p.CreateIP,
+		NotifyURL:      notifyURL,
+		TradeType:      p.TradeType,
+		OpenID:         p.OpenID,
+		SignType:       p.SignType,
+		Detail:         p.Detail,
+		Attach:         p.Attach,
+		GoodsTag:       p.GoodsTag,
+	}
+	rawRet, err := util.PostXML(payGateway, request)
+	if err != nil {
+		return
+	}
+	err = xml.Unmarshal(rawRet, &payOrder)
+	if err != nil {
+		return
+	}
+	if payOrder.ReturnCode == "SUCCESS" {
+		// pay success
+		if payOrder.ResultCode == "SUCCESS" {
+			err = nil
+			return
+		}
+		err = errors.New(payOrder.ErrCode + payOrder.ErrCodeDes)
+		return
+	}
+	err = errors.New("[msg : xmlUnmarshalError] [rawReturn : " + string(rawRet) + "] [params : " + str + "] [sign : " + sign + "]")
+	return
+}
+
+// PrePayID will request wechat merchant api and request for a pre payment order id
+func (o *Order) PrePayID(p *Params) (prePayID string, err error) {
+	order, err := o.PrePayOrder(p)
+	if err != nil {
+		return
+	}
+	if order.PrePayID == "" {
+		err = errors.New("empty prepayid")
+	}
+	prePayID = order.PrePayID
+	return
+}

+ 14 - 268
pay/pay.go

@@ -1,281 +1,27 @@
 package pay
 
 import (
-	"bytes"
-	"crypto/hmac"
-	"crypto/md5"
-	"crypto/sha256"
-	"encoding/hex"
-	"encoding/xml"
-	"errors"
-	"hash"
-	"sort"
-	"strconv"
-	"strings"
-	"time"
-
-	"github.com/silenceper/wechat/context"
-	"github.com/silenceper/wechat/util"
+	"github.com/silenceper/wechat/pay/config"
+	"github.com/silenceper/wechat/pay/notify"
+	"github.com/silenceper/wechat/pay/order"
 )
 
-var payGateway = "https://api.mch.weixin.qq.com/pay/unifiedorder"
-
-// Pay struct extends context
+//Pay 微信支付相关API
 type Pay struct {
-	*context.Context
-}
-
-// Params was NEEDED when request unifiedorder
-// 传入的参数,用于生成 prepay_id 的必需参数
-type Params struct {
-	TotalFee   string
-	CreateIP   string
-	Body       string
-	OutTradeNo string
-	OpenID     string
-	TradeType  string
-	SignType   string
-	Detail     string
-	Attach     string
-	GoodsTag   string
-	NotifyURL  string
-}
-
-// Config 是传出用于 js sdk 用的参数
-type Config struct {
-	Timestamp string `json:"timestamp"`
-	NonceStr  string `json:"nonceStr"`
-	PrePayID  string `json:"prePayId"`
-	SignType  string `json:"signType"`
-	Package   string `json:"package"`
-	PaySign   string `json:"paySign"`
-}
-
-// PreOrder 是 unifie order 接口的返回
-type PreOrder struct {
-	ReturnCode string `xml:"return_code"`
-	ReturnMsg  string `xml:"return_msg"`
-	AppID      string `xml:"appid,omitempty"`
-	MchID      string `xml:"mch_id,omitempty"`
-	NonceStr   string `xml:"nonce_str,omitempty"`
-	Sign       string `xml:"sign,omitempty"`
-	ResultCode string `xml:"result_code,omitempty"`
-	TradeType  string `xml:"trade_type,omitempty"`
-	PrePayID   string `xml:"prepay_id,omitempty"`
-	CodeURL    string `xml:"code_url,omitempty"`
-	ErrCode    string `xml:"err_code,omitempty"`
-	ErrCodeDes string `xml:"err_code_des,omitempty"`
+	cfg *config.Config
 }
 
-// payRequest 接口请求参数
-type payRequest struct {
-	AppID          string `xml:"appid"`
-	MchID          string `xml:"mch_id"`
-	DeviceInfo     string `xml:"device_info,omitempty"`
-	NonceStr       string `xml:"nonce_str"`
-	Sign           string `xml:"sign"`
-	SignType       string `xml:"sign_type,omitempty"`
-	Body           string `xml:"body"`
-	Detail         string `xml:"detail,omitempty"`
-	Attach         string `xml:"attach,omitempty"`      // 附加数据
-	OutTradeNo     string `xml:"out_trade_no"`          // 商户订单号
-	FeeType        string `xml:"fee_type,omitempty"`    // 标价币种
-	TotalFee       string `xml:"total_fee"`             // 标价金额
-	SpbillCreateIP string `xml:"spbill_create_ip"`      // 终端IP
-	TimeStart      string `xml:"time_start,omitempty"`  // 交易起始时间
-	TimeExpire     string `xml:"time_expire,omitempty"` // 交易结束时间
-	GoodsTag       string `xml:"goods_tag,omitempty"`   // 订单优惠标记
-	NotifyURL      string `xml:"notify_url"`            // 通知地址
-	TradeType      string `xml:"trade_type"`            // 交易类型
-	ProductID      string `xml:"product_id,omitempty"`  // 商品ID
-	LimitPay       string `xml:"limit_pay,omitempty"`   //
-	OpenID         string `xml:"openid,omitempty"`      // 用户标识
-	SceneInfo      string `xml:"scene_info,omitempty"`  // 场景信息
-}
-
-// NewPay return an instance of Pay package
-func NewPay(ctx *context.Context) *Pay {
-	pay := Pay{Context: ctx}
-	return &pay
-}
-
-// BridgeConfig get js bridge config
-func (pcf *Pay) BridgeConfig(p *Params) (cfg Config, err error) {
-	var (
-		buffer    strings.Builder
-		h         hash.Hash
-		timestamp = strconv.FormatInt(time.Now().Unix(), 10)
-	)
-	order, err := pcf.PrePayOrder(p)
-	if err != nil {
-		return
-	}
-	buffer.WriteString("appId=")
-	buffer.WriteString(order.AppID)
-	buffer.WriteString("&nonceStr=")
-	buffer.WriteString(order.NonceStr)
-	buffer.WriteString("&package=")
-	buffer.WriteString("prepay_id=" + order.PrePayID)
-	buffer.WriteString("&signType=")
-	buffer.WriteString(p.SignType)
-	buffer.WriteString("&timeStamp=")
-	buffer.WriteString(timestamp)
-	buffer.WriteString("&key=")
-	buffer.WriteString(pcf.PayKey)
-	if p.SignType == "MD5" {
-		h = md5.New()
-	} else {
-		h = hmac.New(sha256.New, []byte(pcf.PayKey))
-	}
-	h.Write([]byte(buffer.String()))
-	// 签名
-	cfg.PaySign = strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
-	cfg.NonceStr = order.NonceStr
-	cfg.Timestamp = timestamp
-	cfg.PrePayID = order.PrePayID
-	cfg.SignType = p.SignType
-	cfg.Package = "prepay_id=" + order.PrePayID
-	return
-}
-
-// PrePayOrder return data for invoke wechat payment
-func (pcf *Pay) PrePayOrder(p *Params) (payOrder PreOrder, err error) {
-	nonceStr := util.RandomStr(32)
-	notifyURL := pcf.PayNotifyURL
-	// 签名类型
-	if p.SignType == "" {
-		p.SignType = "MD5"
-	}
-	// 通知地址
-	if p.NotifyURL != "" {
-		notifyURL = p.NotifyURL
-	}
-	param := make(map[string]interface{})
-	param["appid"] = pcf.AppID
-	param["body"] = p.Body
-	param["mch_id"] = pcf.PayMchID
-	param["nonce_str"] = nonceStr
-	param["out_trade_no"] = p.OutTradeNo
-	param["spbill_create_ip"] = p.CreateIP
-	param["total_fee"] = p.TotalFee
-	param["trade_type"] = p.TradeType
-	param["openid"] = p.OpenID
-	param["sign_type"] = p.SignType
-	param["detail"] = p.Detail
-	param["attach"] = p.Attach
-	param["goods_tag"] = p.GoodsTag
-	param["notify_url"] = notifyURL
-
-	bizKey := "&key=" + pcf.PayKey
-	str := orderParam(param, bizKey)
-	sign := util.MD5Sum(str)
-	request := payRequest{
-		AppID:          pcf.AppID,
-		MchID:          pcf.PayMchID,
-		NonceStr:       nonceStr,
-		Sign:           sign,
-		Body:           p.Body,
-		OutTradeNo:     p.OutTradeNo,
-		TotalFee:       p.TotalFee,
-		SpbillCreateIP: p.CreateIP,
-		NotifyURL:      notifyURL,
-		TradeType:      p.TradeType,
-		OpenID:         p.OpenID,
-		SignType:       p.SignType,
-		Detail:         p.Detail,
-		Attach:         p.Attach,
-		GoodsTag:       p.GoodsTag,
-	}
-	rawRet, err := util.PostXML(payGateway, request)
-	if err != nil {
-		return
-	}
-	err = xml.Unmarshal(rawRet, &payOrder)
-	if err != nil {
-		return
-	}
-	if payOrder.ReturnCode == "SUCCESS" {
-		// pay success
-		if payOrder.ResultCode == "SUCCESS" {
-			err = nil
-			return
-		}
-		err = errors.New(payOrder.ErrCode + payOrder.ErrCodeDes)
-		return
-	}
-	err = errors.New("[msg : xmlUnmarshalError] [rawReturn : " + string(rawRet) + "] [params : " + str + "] [sign : " + sign + "]")
-	return
+//NewPay 实例化微信支付相关API
+func NewPay(cfg *config.Config) *Pay {
+	return &Pay{cfg}
 }
 
-// PrePayID will request wechat merchant api and request for a pre payment order id
-func (pcf *Pay) PrePayID(p *Params) (prePayID string, err error) {
-	order, err := pcf.PrePayOrder(p)
-	if err != nil {
-		return
-	}
-	if order.PrePayID == "" {
-		err = errors.New("empty prepayid")
-	}
-	prePayID = order.PrePayID
-	return
+// GetOrder  下单
+func (pay *Pay) GetOrder() *order.Order {
+	return order.NewOrder(pay.cfg)
 }
 
-// order params
-func orderParam(source interface{}, bizKey string) (returnStr string) {
-	switch v := source.(type) {
-	case map[string]string:
-		keys := make([]string, 0, len(v))
-		for k := range v {
-			if k == "sign" {
-				continue
-			}
-			keys = append(keys, k)
-		}
-		sort.Strings(keys)
-		var buf bytes.Buffer
-		for _, k := range keys {
-			if v[k] == "" {
-				continue
-			}
-			if buf.Len() > 0 {
-				buf.WriteByte('&')
-			}
-			buf.WriteString(k)
-			buf.WriteByte('=')
-			buf.WriteString(v[k])
-		}
-		buf.WriteString(bizKey)
-		returnStr = buf.String()
-	case map[string]interface{}:
-		keys := make([]string, 0, len(v))
-		for k := range v {
-			if k == "sign" {
-				continue
-			}
-			keys = append(keys, k)
-		}
-		sort.Strings(keys)
-		var buf bytes.Buffer
-		for _, k := range keys {
-			if v[k] == "" {
-				continue
-			}
-			if buf.Len() > 0 {
-				buf.WriteByte('&')
-			}
-			buf.WriteString(k)
-			buf.WriteByte('=')
-			switch vv := v[k].(type) {
-			case string:
-				buf.WriteString(vv)
-			case int:
-				buf.WriteString(strconv.FormatInt(int64(vv), 10))
-			default:
-				panic("params type not supported")
-			}
-		}
-		buf.WriteString(bizKey)
-		returnStr = buf.String()
-	}
-	return
+// GetNotify  通知
+func (pay *Pay) GetNotify() *notify.Notify {
+	return notify.NewNotify(pay.cfg)
 }

+ 28 - 16
pay/refund.go

@@ -1,16 +1,28 @@
-package pay
+package refund
 
 import (
 	"encoding/xml"
 	"fmt"
 
+	"github.com/silenceper/wechat/pay/config"
 	"github.com/silenceper/wechat/util"
 )
 
 var refundGateway = "https://api.mch.weixin.qq.com/secapi/pay/refund"
 
-//RefundParams 调用参数
-type RefundParams struct {
+// Refund struct extends context
+type Refund struct {
+	*config.Config
+}
+
+// NewRefund return an instance of refund package
+func NewRefund(cfg *config.Config) *Refund {
+	refund := Refund{cfg}
+	return &refund
+}
+
+//Params 调用参数
+type Params struct {
 	TransactionID string
 	OutRefundNo   string
 	TotalFee      string
@@ -19,8 +31,8 @@ type RefundParams struct {
 	RootCa        string //ca证书
 }
 
-//refundRequest 接口请求参数
-type refundRequest struct {
+//request 接口请求参数
+type request struct {
 	AppID         string `xml:"appid"`
 	MchID         string `xml:"mch_id"`
 	NonceStr      string `xml:"nonce_str"`
@@ -34,8 +46,8 @@ type refundRequest struct {
 	//NotifyUrl     string `xml:"notify_url,omitempty"`
 }
 
-//RefundResponse 接口返回
-type RefundResponse struct {
+//Response 接口返回
+type Response struct {
 	ReturnCode          string `xml:"return_code"`
 	ReturnMsg           string `xml:"return_msg"`
 	AppID               string `xml:"appid,omitempty"`
@@ -59,11 +71,11 @@ type RefundResponse struct {
 }
 
 //Refund 退款申请
-func (pcf *Pay) Refund(p *RefundParams) (rsp RefundResponse, err error) {
+func (refund *Refund) Refund(p *Params) (rsp Response, err error) {
 	nonceStr := util.RandomStr(32)
 	param := make(map[string]interface{})
-	param["appid"] = pcf.AppID
-	param["mch_id"] = pcf.PayMchID
+	param["appid"] = refund.AppID
+	param["mch_id"] = refund.MchID
 	param["nonce_str"] = nonceStr
 	param["out_refund_no"] = p.OutRefundNo
 	param["refund_desc"] = p.RefundDesc
@@ -72,12 +84,12 @@ func (pcf *Pay) Refund(p *RefundParams) (rsp RefundResponse, err error) {
 	param["sign_type"] = "MD5"
 	param["transaction_id"] = p.TransactionID
 
-	bizKey := "&key=" + pcf.PayKey
-	str := orderParam(param, bizKey)
+	bizKey := "&key=" + refund.Key
+	str := util.OrderParam(param, bizKey)
 	sign := util.MD5Sum(str)
-	request := refundRequest{
-		AppID:         pcf.AppID,
-		MchID:         pcf.PayMchID,
+	request := request{
+		AppID:         refund.AppID,
+		MchID:         refund.MchID,
 		NonceStr:      nonceStr,
 		Sign:          sign,
 		SignType:      "MD5",
@@ -87,7 +99,7 @@ func (pcf *Pay) Refund(p *RefundParams) (rsp RefundResponse, err error) {
 		RefundFee:     p.RefundFee,
 		RefundDesc:    p.RefundDesc,
 	}
-	rawRet, err := util.PostXMLWithTLS(refundGateway, request, p.RootCa, pcf.PayMchID)
+	rawRet, err := util.PostXMLWithTLS(refundGateway, request, p.RootCa, refund.MchID)
 	if err != nil {
 		return
 	}

+ 67 - 0
util/param.go

@@ -0,0 +1,67 @@
+package util
+
+import (
+	"bytes"
+	"sort"
+	"strconv"
+)
+
+// OrderParam order params
+func OrderParam(source interface{}, bizKey string) (returnStr string) {
+	switch v := source.(type) {
+	case map[string]string:
+		keys := make([]string, 0, len(v))
+		for k := range v {
+			if k == "sign" {
+				continue
+			}
+			keys = append(keys, k)
+		}
+		sort.Strings(keys)
+		var buf bytes.Buffer
+		for _, k := range keys {
+			if v[k] == "" {
+				continue
+			}
+			if buf.Len() > 0 {
+				buf.WriteByte('&')
+			}
+			buf.WriteString(k)
+			buf.WriteByte('=')
+			buf.WriteString(v[k])
+		}
+		buf.WriteString(bizKey)
+		returnStr = buf.String()
+	case map[string]interface{}:
+		keys := make([]string, 0, len(v))
+		for k := range v {
+			if k == "sign" {
+				continue
+			}
+			keys = append(keys, k)
+		}
+		sort.Strings(keys)
+		var buf bytes.Buffer
+		for _, k := range keys {
+			if v[k] == "" {
+				continue
+			}
+			if buf.Len() > 0 {
+				buf.WriteByte('&')
+			}
+			buf.WriteString(k)
+			buf.WriteByte('=')
+			switch vv := v[k].(type) {
+			case string:
+				buf.WriteString(vv)
+			case int:
+				buf.WriteString(strconv.FormatInt(int64(vv), 10))
+			default:
+				panic("params type not supported")
+			}
+		}
+		buf.WriteString(bizKey)
+		returnStr = buf.String()
+	}
+	return
+}

+ 18 - 101
wechat.go

@@ -1,125 +1,42 @@
 package wechat
 
 import (
-	"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"
+	miniConfig "github.com/silenceper/wechat/miniprogram/config"
+	payConfig "github.com/silenceper/wechat/pay/config"
+
+	"github.com/silenceper/wechat/officialaccount"
+	offConfig "github.com/silenceper/wechat/officialaccount/config"
 	"github.com/silenceper/wechat/pay"
-	"github.com/silenceper/wechat/qr"
-	"github.com/silenceper/wechat/server"
-	"github.com/silenceper/wechat/tcb"
-	"github.com/silenceper/wechat/user"
 )
 
 // Wechat struct
 type Wechat struct {
-	Context *context.Context
 }
 
 // Config for user
 type Config struct {
-	AppID          string
-	AppSecret      string
-	Token          string
-	EncodingAESKey string
-	PayMchID       string //支付 - 商户 ID
-	PayNotifyURL   string //支付 - 接受微信支付结果通知的接口地址
-	PayKey         string //支付 - 商户后台设置的支付 key
-	Cache          cache.Cache
+	PayMchID     string //支付 - 商户 ID
+	PayNotifyURL string //支付 - 接受微信支付结果通知的接口地址
+	PayKey       string //支付 - 商户后台设置的支付 key
 }
 
 // NewWechat init
-func NewWechat(cfg *Config) *Wechat {
-	context := new(context.Context)
-	copyConfigToContext(cfg, context)
-	return &Wechat{context}
-}
-
-func copyConfigToContext(cfg *Config, context *context.Context) {
-	context.AppID = cfg.AppID
-	context.AppSecret = cfg.AppSecret
-	context.Token = cfg.Token
-	context.EncodingAESKey = cfg.EncodingAESKey
-	context.PayMchID = cfg.PayMchID
-	context.PayKey = cfg.PayKey
-	context.PayNotifyURL = cfg.PayNotifyURL
-	context.Cache = cfg.Cache
-	context.SetAccessTokenLock(new(sync.RWMutex))
-	context.SetJsAPITicketLock(new(sync.RWMutex))
-}
-
-// 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)
-}
-
-//GetAccessToken 获取access_token
-func (wc *Wechat) GetAccessToken() (string, error) {
-	return wc.Context.GetAccessToken()
-}
-
-// GetOauth oauth2网页授权
-func (wc *Wechat) GetOauth() *oauth.Oauth {
-	return oauth.NewOauth(wc.Context)
-}
-
-// GetMaterial 素材管理
-func (wc *Wechat) GetMaterial() *material.Material {
-	return material.NewMaterial(wc.Context)
+func NewWechat() *Wechat {
+	return &Wechat{}
 }
 
-// GetJs js-sdk配置
-func (wc *Wechat) GetJs() *js.Js {
-	return js.NewJs(wc.Context)
-}
-
-// GetMenu 菜单管理接口
-func (wc *Wechat) GetMenu() *menu.Menu {
-	return menu.NewMenu(wc.Context)
-}
-
-// GetUser 用户管理接口
-func (wc *Wechat) GetUser() *user.User {
-	return user.NewUser(wc.Context)
-}
-
-// GetTemplate 模板消息接口
-func (wc *Wechat) GetTemplate() *message.Template {
-	return message.NewTemplate(wc.Context)
-}
-
-// GetPay 返回支付消息的实例
-func (wc *Wechat) GetPay() *pay.Pay {
-	return pay.NewPay(wc.Context)
-}
-
-// GetQR 返回二维码的实例
-func (wc *Wechat) GetQR() *qr.QR {
-	return qr.NewQR(wc.Context)
+//GetOfficialAccount 获取微信公众号实例
+func (wc *Wechat) GetOfficialAccount(cfg *offConfig.Config) *officialaccount.OfficialAccount {
+	return officialaccount.NewOfficialAccount(cfg)
 }
 
 // GetMiniProgram 获取小程序的实例
-func (wc *Wechat) GetMiniProgram() *miniprogram.MiniProgram {
-	return miniprogram.NewMiniProgram(wc.Context)
-}
-
-// GetDevice 获取智能设备的实例
-func (wc *Wechat) GetDevice() *device.Device {
-	return device.NewDevice(wc.Context)
+func (wc *Wechat) GetMiniProgram(cfg *miniConfig.Config) *miniprogram.MiniProgram {
+	return miniprogram.NewMiniProgram(cfg)
 }
 
-// GetTcb 获取小程序-云开发的实例
-func (wc *Wechat) GetTcb() *tcb.Tcb {
-	return tcb.NewTcb(wc.Context)
+// GetPay 获取微信支付的实例
+func (wc *Wechat) GetPay(cfg *payConfig.Config) *pay.Pay {
+	return pay.NewPay(cfg)
 }

+ 5 - 0
work/README.md

@@ -0,0 +1,5 @@
+# 企业微信
+
+[官方文档](https://work.weixin.qq.com/api/doc)
+
+## 快速入门