فهرست منبع

Merge pull request #108 from JefferyWang/master

增加部分小程序接口
silenceper 6 سال پیش
والد
کامیت
6e1ec1f00c
8فایلهای تغییر یافته به همراه683 افزوده شده و 0 حذف شده
  1. 105 0
      README.md
  2. 305 0
      miniprogram/analysis.go
  3. 93 0
      miniprogram/decrypt.go
  4. 17 0
      miniprogram/miniprogram.go
  5. 91 0
      miniprogram/qrcode.go
  6. 40 0
      miniprogram/sns.go
  7. 26 0
      util/http.go
  8. 6 0
      wechat.go

+ 105 - 0
README.md

@@ -96,6 +96,7 @@ Cache主要用来保存全局access_token以及js-sdk中的ticket:
 		- 检验access_token是否有效
 	- 获取js-sdk配置
 - [素材管理](#素材管理)
+- [小程序开发](#小程序开发)
 
 ## 消息管理
 
@@ -529,6 +530,110 @@ type Config struct {
 
 [素材管理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)

+ 305 - 0
miniprogram/analysis.go

@@ -0,0 +1,305 @@
+package miniprogram
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"github.com/silenceper/wechat/util"
+)
+
+const (
+	// 获取用户访问小程序日留存
+	getAnalysisDailyRetainURL = "https://api.weixin.qq.com/datacube/getweanalysisappiddailyretaininfo?access_token=%s"
+	// 获取用户访问小程序月留存
+	getAnalysisMonthlyRetainURL = "https://api.weixin.qq.com/datacube/getweanalysisappidmonthlyretaininfo?access_token=%s"
+	// 获取用户访问小程序周留存
+	getAnalysisWeeklyRetainURL = "https://api.weixin.qq.com/datacube/getweanalysisappidweeklyretaininfo?access_token=%s"
+	// 获取用户访问小程序数据概况
+	getAnalysisDailySummaryURL = "https://api.weixin.qq.com/datacube/getweanalysisappiddailysummarytrend?access_token=%s"
+	// 获取用户访问小程序数据日趋势
+	getAnalysisDailyVisitTrendURL = "https://api.weixin.qq.com/datacube/getweanalysisappiddailyvisittrend?access_token=%s"
+	// 获取用户访问小程序数据月趋势
+	getAnalysisMonthlyVisitTrendURL = "https://api.weixin.qq.com/datacube/getweanalysisappidmonthlyvisittrend?access_token=%s"
+	// 获取用户访问小程序数据周趋势
+	getAnalysisWeeklyVisitTrendURL = "https://api.weixin.qq.com/datacube/getweanalysisappidweeklyvisittrend?access_token=%s"
+	// 获取小程序新增或活跃用户的画像分布数据
+	getAnalysisUserPortraitURL = "https://api.weixin.qq.com/datacube/getweanalysisappiduserportrait?access_token=%s"
+	// 获取用户小程序访问分布数据
+	getAnalysisVisitDistributionURL = "https://api.weixin.qq.com/datacube/getweanalysisappidvisitdistribution?access_token=%s"
+	// 访问页面
+	getAnalysisVisitPageURL = "https://api.weixin.qq.com/datacube/getweanalysisappidvisitpage?access_token=%s"
+)
+
+// fetchData 拉取统计数据
+func (wxa *MiniProgram) fetchData(urlStr string, body interface{}) (response []byte, err error) {
+	var accessToken string
+	accessToken, err = wxa.GetAccessToken()
+	if err != nil {
+		return
+	}
+	urlStr = fmt.Sprintf(urlStr, accessToken)
+	response, err = util.PostJSON(urlStr, body)
+	return
+}
+
+// AnalysisRetainItem 留存项结构
+type AnalysisRetainItem struct {
+	Key   int `json:"key"`   // 标识,0开始表示当天,1表示1甜后,以此类推
+	Value int `json:"value"` // key对应日期的新增用户数/活跃用户数(key=0时)或留存用户数(k>0时)
+}
+
+// ResAnalysisRetain 小程序留存数据返回
+type ResAnalysisRetain struct {
+	util.CommonError
+	RefDate    string               `json:"ref_date"`     // 日期
+	VisitUVNew []AnalysisRetainItem `json:"visit_uv_new"` // 新增用户留存
+	VisitUV    []AnalysisRetainItem `json:"visit_uv"`     // 活跃用户留存
+}
+
+// getAnalysisRetain 获取用户访问小程序留存数据(日、月、周)
+func (wxa *MiniProgram) 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)
+	if err != nil {
+		return
+	}
+	err = json.Unmarshal(response, &result)
+	if err != nil {
+		return
+	}
+	if result.ErrCode != 0 {
+		err = fmt.Errorf("getAnalysisRetain error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
+		return
+	}
+	return
+}
+
+// GetAnalysisDailyRetain 获取用户访问小程序日留存
+func (wxa *MiniProgram) GetAnalysisDailyRetain(beginDate, endDate string) (result ResAnalysisRetain, err error) {
+	return wxa.getAnalysisRetain(getAnalysisDailyRetainURL, beginDate, endDate)
+}
+
+// GetAnalysisMonthlyRetain 获取用户访问小程序月留存
+func (wxa *MiniProgram) GetAnalysisMonthlyRetain(beginDate, endDate string) (result ResAnalysisRetain, err error) {
+	return wxa.getAnalysisRetain(getAnalysisMonthlyRetainURL, beginDate, endDate)
+}
+
+// GetAnalysisWeeklyRetain 获取用户访问小程序周留存
+func (wxa *MiniProgram) GetAnalysisWeeklyRetain(beginDate, endDate string) (result ResAnalysisRetain, err error) {
+	return wxa.getAnalysisRetain(getAnalysisWeeklyRetainURL, beginDate, endDate)
+}
+
+// ResAnalysisDailySummary 小程序访问数据概况
+type ResAnalysisDailySummary struct {
+	util.CommonError
+	List []struct {
+		RefDate    string `json:"ref_date"`    // 日期
+		VisitTotal int    `json:"visit_total"` // 累计用户数
+		SharePV    int    `json:"share_pv"`    // 转发次数
+		ShareUV    int    `json:"share_uv"`    // 转发人数
+	} `json:"list"`
+}
+
+// GetAnalysisDailySummary 获取用户访问小程序数据概况
+func (wxa *MiniProgram) GetAnalysisDailySummary(beginDate, endDate string) (result ResAnalysisDailySummary, err error) {
+	body := map[string]string{
+		"begin_date": beginDate,
+		"end_date":   endDate,
+	}
+	response, err := wxa.fetchData(getAnalysisDailySummaryURL, body)
+	if err != nil {
+		return
+	}
+	fmt.Println(string(response))
+	err = json.Unmarshal(response, &result)
+	if err != nil {
+		return
+	}
+	if result.ErrCode != 0 {
+		err = fmt.Errorf("GetAnalysisDailySummary error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
+		return
+	}
+	return
+}
+
+// ResAnalysisVisitTrend 小程序访问数据趋势(日、月、周)
+type ResAnalysisVisitTrend struct {
+	util.CommonError
+	List []struct {
+		RefDate         string  `json:"ref_date"`          // 日期
+		SessionCnt      int     `json:"session_cnt"`       // 打开次数
+		VisitPV         int     `json:"visit_pv"`          // 访问次数
+		VisitUV         int     `json:"visit_uv"`          // 访问人数
+		VisitUVNew      int     `json:"visit_uv_new"`      // 新用户数
+		StayTimeUV      float64 `json:"stay_time_uv"`      // 人均停留时长
+		StayTimeSession float64 `json:"stay_time_session"` // 次均停留时常
+		VisitDepth      float64 `json:"visit_depth"`       // 平均访问深度
+	} `json:"list"`
+}
+
+// getAnalysisRetain 获取小程序访问数据趋势(日、月、周)
+func (wxa *MiniProgram) 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)
+	if err != nil {
+		return
+	}
+	err = json.Unmarshal(response, &result)
+	if err != nil {
+		return
+	}
+	if result.ErrCode != 0 {
+		err = fmt.Errorf("getAnalysisVisitTrend error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
+		return
+	}
+	return
+}
+
+// GetAnalysisDailyVisitTrend 获取用户访问小程序数据日趋势
+func (wxa *MiniProgram) GetAnalysisDailyVisitTrend(beginDate, endDate string) (result ResAnalysisVisitTrend, err error) {
+	return wxa.getAnalysisVisitTrend(getAnalysisDailyVisitTrendURL, beginDate, endDate)
+}
+
+// GetAnalysisMonthlyVisitTrend 获取用户访问小程序数据月趋势
+func (wxa *MiniProgram) GetAnalysisMonthlyVisitTrend(beginDate, endDate string) (result ResAnalysisVisitTrend, err error) {
+	return wxa.getAnalysisVisitTrend(getAnalysisMonthlyVisitTrendURL, beginDate, endDate)
+}
+
+// GetAnalysisWeeklyVisitTrend 获取用户访问小程序数据周趋势
+func (wxa *MiniProgram) GetAnalysisWeeklyVisitTrend(beginDate, endDate string) (result ResAnalysisVisitTrend, err error) {
+	return wxa.getAnalysisVisitTrend(getAnalysisWeeklyVisitTrendURL, beginDate, endDate)
+}
+
+// UserPortraitItem 用户画像项目
+type UserPortraitItem struct {
+	ID                  int    `json:"id"`                     // 属性值id
+	Name                string `json:"name"`                   // 属性值名称
+	AccessSourceVisitUV int    `json:"access_source_visit_uv"` // 该场景访问uv
+}
+
+// UserPortrait 用户画像
+type UserPortrait struct {
+	Index     int                `json:"index"`     // 分布类型
+	Province  []UserPortraitItem `json:"province"`  // 省份,如北京、广东等
+	City      []UserPortraitItem `json:"city"`      // 城市,如北京、广州等
+	Genders   []UserPortraitItem `json:"genders"`   // 性别,包括男、女、未知
+	Platforms []UserPortraitItem `json:"platforms"` // 终端类型,包括iPhone, android, 其他
+	Devices   []UserPortraitItem `json:"devices"`   // 机型,如苹果iPhone 6, OPPO R9等
+	Ages      []UserPortraitItem `json:"ages"`      // 年龄,包括17岁以下、18-24对等区间
+}
+
+// ResAnalysisUserPortrait 小程序新增或活跃用户的画像分布数据返回
+type ResAnalysisUserPortrait struct {
+	util.CommonError
+	RefDate    string       `json:"ref_date"`     // 日期
+	VisitUVNew UserPortrait `json:"visit_uv_new"` // 新用户画像
+	VisitUV    UserPortrait `json:"visit_uv"`     // 活跃用户画像
+}
+
+// GetAnalysisUserPortrait 获取小程序新增或活跃用户的画像分布数据
+func (wxa *MiniProgram) GetAnalysisUserPortrait(beginDate, endDate string) (result ResAnalysisUserPortrait, err error) {
+	body := map[string]string{
+		"begin_date": beginDate,
+		"end_date":   endDate,
+	}
+	response, err := wxa.fetchData(getAnalysisUserPortraitURL, body)
+	if err != nil {
+		return
+	}
+	err = json.Unmarshal(response, &result)
+	if err != nil {
+		return
+	}
+	if result.ErrCode != 0 {
+		err = fmt.Errorf("GetAnalysisUserPortrait error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
+		return
+	}
+	return
+}
+
+// VisitDistributionIndexItem 访问分数数据结构
+type VisitDistributionIndexItem struct {
+	Key                 int `json:"key"`                    // 场景id
+	Value               int `json:"value"`                  // 该场景id访问pv
+	AccessSourceVisitUV int `json:"access_source_visit_uv"` // 该场景id访问uv
+}
+
+// VisitDistributionIndex 访问分布单分布类型数据
+type VisitDistributionIndex struct {
+	Index    string                       `json:"index"`     // 分布类型
+	ItemList []VisitDistributionIndexItem `json:"item_list"` // 分布数据列表
+}
+
+// ResAnalysisVisitDistribution 小程序访问分布数据返回
+type ResAnalysisVisitDistribution struct {
+	util.CommonError
+	RefDate string                   `json:"ref_date"` // 日期
+	List    []VisitDistributionIndex `json:"list"`     // 数据列表
+}
+
+// GetAnalysisVisitDistribution 获取用户小程序访问分布数据
+func (wxa *MiniProgram) GetAnalysisVisitDistribution(beginDate, endDate string) (result ResAnalysisVisitDistribution, err error) {
+	body := map[string]string{
+		"begin_date": beginDate,
+		"end_date":   endDate,
+	}
+	response, err := wxa.fetchData(getAnalysisVisitDistributionURL, body)
+	if err != nil {
+		return
+	}
+	err = json.Unmarshal(response, &result)
+	if err != nil {
+		return
+	}
+	if result.ErrCode != 0 {
+		err = fmt.Errorf("GetAnalysisVisitDistribution error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
+		return
+	}
+	return
+}
+
+// VisitPageItem 访问单个页面的数据结构
+type VisitPageItem struct {
+	PagePath       string  `json:"page_path"`        // 页面路径
+	PageVisitPV    int     `json:"page_visit_pv"`    // 访问次数
+	PageVisitUV    int     `json:"page_visit_uv"`    // 访问人数
+	PageStaytimePV float64 `json:"page_staytime_pv"` // 次均停留时常
+	EntrypagePV    int     `json:"entrypage_pv"`     // 进入页次数
+	ExitpagePV     int     `json:"exitpage_pv"`      // 退出页次数
+	PageSharePV    int     `json:"page_share_pv"`    // 转发次数
+	PageShareUV    int     `json:"page_share_uv"`    // 转发人数
+}
+
+// ResAnalysisVisitPage 访问小程序页面访问数据返回
+type ResAnalysisVisitPage struct {
+	util.CommonError
+	RefDate string          `json:"ref_date"` // 日期
+	List    []VisitPageItem `json:"list"`     // 数据列表
+}
+
+// GetAnalysisVisitPage 获取小程序页面访问数据
+func (wxa *MiniProgram) GetAnalysisVisitPage(beginDate, endDate string) (result ResAnalysisVisitPage, err error) {
+	body := map[string]string{
+		"begin_date": beginDate,
+		"end_date":   endDate,
+	}
+	response, err := wxa.fetchData(getAnalysisVisitPageURL, body)
+	if err != nil {
+		return
+	}
+	err = json.Unmarshal(response, &result)
+	if err != nil {
+		return
+	}
+	if result.ErrCode != 0 {
+		err = fmt.Errorf("GetAnalysisVisitPage error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
+		return
+	}
+	return
+}

+ 93 - 0
miniprogram/decrypt.go

@@ -0,0 +1,93 @@
+package miniprogram
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"encoding/base64"
+	"encoding/json"
+	"errors"
+)
+
+var (
+	// ErrAppIDNotMatch appid不匹配
+	ErrAppIDNotMatch = errors.New("app id not match")
+	// ErrInvalidBlockSize block size不合法
+	ErrInvalidBlockSize = errors.New("invalid block size")
+	// ErrInvalidPKCS7Data PKCS7数据不合法
+	ErrInvalidPKCS7Data = errors.New("invalid PKCS7 data")
+	// ErrInvalidPKCS7Padding 输入padding失败
+	ErrInvalidPKCS7Padding = errors.New("invalid padding on input")
+)
+
+// UserInfo 用户信息
+type UserInfo struct {
+	OpenID    string `json:"openId"`
+	UnionID   string `json:"unionId"`
+	NickName  string `json:"nickName"`
+	Gender    int    `json:"gender"`
+	City      string `json:"city"`
+	Province  string `json:"province"`
+	Country   string `json:"country"`
+	AvatarURL string `json:"avatarUrl"`
+	Language  string `json:"language"`
+	Watermark struct {
+		Timestamp int64  `json:"timestamp"`
+		AppID     string `json:"appid"`
+	} `json:"watermark"`
+}
+
+// pkcs7Unpad returns slice of the original data without padding
+func pkcs7Unpad(data []byte, blockSize int) ([]byte, error) {
+	if blockSize <= 0 {
+		return nil, ErrInvalidBlockSize
+	}
+	if len(data)%blockSize != 0 || len(data) == 0 {
+		return nil, ErrInvalidPKCS7Data
+	}
+	c := data[len(data)-1]
+	n := int(c)
+	if n == 0 || n > len(data) {
+		return nil, ErrInvalidPKCS7Padding
+	}
+	for i := 0; i < n; i++ {
+		if data[len(data)-n+i] != c {
+			return nil, ErrInvalidPKCS7Padding
+		}
+	}
+	return data[:len(data)-n], nil
+}
+
+// Decrypt 解密数据
+func (wxa *MiniProgram) Decrypt(sessionKey, encryptedData, iv string) (*UserInfo, error) {
+	aesKey, err := base64.StdEncoding.DecodeString(sessionKey)
+	if err != nil {
+		return nil, err
+	}
+	cipherText, err := base64.StdEncoding.DecodeString(encryptedData)
+	if err != nil {
+		return nil, err
+	}
+	ivBytes, err := base64.StdEncoding.DecodeString(iv)
+	if err != nil {
+		return nil, err
+	}
+	block, err := aes.NewCipher(aesKey)
+	if err != nil {
+		return nil, err
+	}
+	mode := cipher.NewCBCDecrypter(block, ivBytes)
+	mode.CryptBlocks(cipherText, cipherText)
+	cipherText, err = pkcs7Unpad(cipherText, block.BlockSize())
+	if err != nil {
+		return nil, err
+	}
+	var userInfo UserInfo
+	err = json.Unmarshal(cipherText, &userInfo)
+	if err != nil {
+		return nil, err
+	}
+	if userInfo.Watermark.AppID != wxa.AppID {
+		return nil, ErrAppIDNotMatch
+	}
+	return &userInfo, nil
+}

+ 17 - 0
miniprogram/miniprogram.go

@@ -0,0 +1,17 @@
+package miniprogram
+
+import (
+	"github.com/silenceper/wechat/context"
+)
+
+// MiniProgram struct extends context
+type MiniProgram struct {
+	*context.Context
+}
+
+// NewMiniProgram 实例化小程序接口
+func NewMiniProgram(context *context.Context) *MiniProgram {
+	miniProgram := new(MiniProgram)
+	miniProgram.Context = context
+	return miniProgram
+}

+ 91 - 0
miniprogram/qrcode.go

@@ -0,0 +1,91 @@
+package miniprogram
+
+import (
+	"encoding/json"
+	"fmt"
+	"strings"
+
+	"github.com/JefferyWang/wechat/util"
+)
+
+const (
+	createWXAQRCodeURL   = "https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=%s"
+	getWXACodeURL        = "https://api.weixin.qq.com/wxa/getwxacode?access_token=%s"
+	getWXACodeUnlimitURL = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s"
+)
+
+// QRCoder 小程序码参数
+type QRCoder struct {
+	// page 必须是已经发布的小程序存在的页面,根路径前不要填加 /,不能携带参数(参数请放在scene字段里),如果不填写这个字段,默认跳主页面
+	Page string `json:"page,omitempty"`
+	// path 扫码进入的小程序页面路径
+	Path string `json:"path,omitempty"`
+	// width 图片宽度
+	Width int `json:"width,omitempty"`
+	// scene 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~,其它字符请自行编码为合法字符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式)
+	Scene string `json:"scene,omitempty"`
+	// autoColor 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调
+	AutoColor bool `json:"auto_color,omitempty"`
+	// lineColor AutoColor 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"},十进制表示
+	LineColor Color `json:"line_color,omitempty"`
+	// isHyaline 是否需要透明底色
+	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) {
+	var accessToken string
+	accessToken, err = wxa.GetAccessToken()
+	if err != nil {
+		return
+	}
+
+	urlStr = fmt.Sprintf(urlStr, accessToken)
+	var contentType string
+	response, contentType, err = util.PostJSONWithRespContentType(urlStr, body)
+	if err != nil {
+		return
+	}
+	if strings.HasPrefix(contentType, "application/json") {
+		// 返回错误信息
+		var result util.CommonError
+		err = json.Unmarshal(response, &result)
+		if err == nil && result.ErrCode != 0 {
+			err = fmt.Errorf("fetchCode error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
+			return nil, err
+		}
+	} else if contentType == "image/jpeg" {
+		// 返回文件
+		return response, nil
+	} else {
+		err = fmt.Errorf("fetchCode error : unknown response content type - %v", contentType)
+		return nil, err
+	}
+
+	return
+}
+
+// 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)
+}
+
+// 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)
+}
+
+// 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)
+}

+ 40 - 0
miniprogram/sns.go

@@ -0,0 +1,40 @@
+package miniprogram
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"github.com/silenceper/wechat/util"
+)
+
+const (
+	code2SessionURL = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code"
+)
+
+// ResCode2Session 登录凭证校验的返回结果
+type ResCode2Session struct {
+	util.CommonError
+
+	OpenID     string `json:"openid"`      // 用户唯一标识
+	SessionKey string `json:"session_key"` // 会话密钥
+	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)
+	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("Code2Session error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
+		return
+	}
+	return
+}

+ 26 - 0
util/http.go

@@ -50,6 +50,32 @@ func PostJSON(uri string, obj interface{}) ([]byte, error) {
 	return ioutil.ReadAll(response.Body)
 }
 
+// PostJSONWithRespContentType post json数据请求,且返回数据类型
+func PostJSONWithRespContentType(uri string, obj interface{}) ([]byte, string, error) {
+	jsonData, err := json.Marshal(obj)
+	if err != nil {
+		return nil, "", err
+	}
+
+	jsonData = bytes.Replace(jsonData, []byte("\\u003c"), []byte("<"), -1)
+	jsonData = bytes.Replace(jsonData, []byte("\\u003e"), []byte(">"), -1)
+	jsonData = bytes.Replace(jsonData, []byte("\\u0026"), []byte("&"), -1)
+
+	body := bytes.NewBuffer(jsonData)
+	response, err := http.Post(uri, "application/json;charset=utf-8", body)
+	if err != nil {
+		return nil, "", err
+	}
+	defer response.Body.Close()
+
+	if response.StatusCode != http.StatusOK {
+		return nil, "", fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode)
+	}
+	responseData, err := ioutil.ReadAll(response.Body)
+	contentType := response.Header.Get("Content-Type")
+	return responseData, contentType, err
+}
+
 //PostFile 上传文件
 func PostFile(fieldname, filename, uri string) ([]byte, error) {
 	fields := []MultipartFormField{

+ 6 - 0
wechat.go

@@ -4,6 +4,7 @@ import (
 	"net/http"
 	"sync"
 
+	"github.com/JefferyWang/wechat/miniprogram"
 	"github.com/silenceper/wechat/cache"
 	"github.com/silenceper/wechat/context"
 	"github.com/silenceper/wechat/js"
@@ -99,3 +100,8 @@ func (wc *Wechat) GetTemplate() *template.Template {
 func (wc *Wechat) GetPay() *pay.Pay {
 	return pay.NewPay(wc.Context)
 }
+
+// GetMiniProgram 获取小程序的实例
+func (wc *Wechat) GetMiniProgram() *miniprogram.MiniProgram {
+	return miniprogram.NewMiniProgram(wc.Context)
+}