From eda287070d6e176e9f937b695ad39df62f78d2f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jefferwang=28=E7=8E=8B=E4=BF=8A=E9=94=8B=29?= Date: Thu, 14 Feb 2019 16:07:24 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=B0=8F=E7=A8=8B=E5=BA=8F=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- miniprogram/analysis.go | 305 +++++++++++++++++++++++++++++++++++++ miniprogram/miniprogram.go | 17 +++ miniprogram/qrcode.go | 91 +++++++++++ miniprogram/sns.go | 40 +++++ wechat.go | 6 + 5 files changed, 459 insertions(+) create mode 100644 miniprogram/analysis.go create mode 100644 miniprogram/miniprogram.go create mode 100644 miniprogram/qrcode.go create mode 100644 miniprogram/sns.go diff --git a/miniprogram/analysis.go b/miniprogram/analysis.go new file mode 100644 index 0000000..a87d53a --- /dev/null +++ b/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 +} diff --git a/miniprogram/miniprogram.go b/miniprogram/miniprogram.go new file mode 100644 index 0000000..7b5535c --- /dev/null +++ b/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 +} diff --git a/miniprogram/qrcode.go b/miniprogram/qrcode.go new file mode 100644 index 0000000..d73651c --- /dev/null +++ b/miniprogram/qrcode.go @@ -0,0 +1,91 @@ +package miniprogram + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/silenceper/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) +} diff --git a/miniprogram/sns.go b/miniprogram/sns.go new file mode 100644 index 0000000..03b939b --- /dev/null +++ b/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 +} diff --git a/wechat.go b/wechat.go index 19856b8..b487404 100644 --- a/wechat.go +++ b/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) +} From 61476d351dee8b3b511f0e1ed5718c8caf276c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jefferwang=28=E7=8E=8B=E4=BF=8A=E9=94=8B=29?= Date: Tue, 19 Feb 2019 15:11:45 +0800 Subject: [PATCH 2/4] add PostJSONWithRespContentType --- miniprogram/qrcode.go | 2 +- util/http.go | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/miniprogram/qrcode.go b/miniprogram/qrcode.go index d73651c..a7f3aac 100644 --- a/miniprogram/qrcode.go +++ b/miniprogram/qrcode.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/silenceper/wechat/util" + "github.com/JefferyWang/wechat/util" ) const ( diff --git a/util/http.go b/util/http.go index 6881052..006f916 100644 --- a/util/http.go +++ b/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{ From 5677b60759a3922e30ba872ba34fe4572275d5eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jefferwang=28=E7=8E=8B=E4=BF=8A=E9=94=8B=29?= Date: Tue, 19 Feb 2019 16:41:15 +0800 Subject: [PATCH 3/4] update readme and some comments --- README.md | 105 ++++++++++++++++++++++++++++++++++++++++ miniprogram/analysis.go | 6 +-- 2 files changed, 108 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 030926c..adfc050 100644 --- a/README.md +++ b/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) diff --git a/miniprogram/analysis.go b/miniprogram/analysis.go index a87d53a..4c56894 100644 --- a/miniprogram/analysis.go +++ b/miniprogram/analysis.go @@ -87,7 +87,7 @@ func (wxa *MiniProgram) GetAnalysisMonthlyRetain(beginDate, endDate string) (res return wxa.getAnalysisRetain(getAnalysisMonthlyRetainURL, beginDate, endDate) } -// GetAnalysisWeeklyRetain 获取用户访问小程序日留存 +// GetAnalysisWeeklyRetain 获取用户访问小程序周留存 func (wxa *MiniProgram) GetAnalysisWeeklyRetain(beginDate, endDate string) (result ResAnalysisRetain, err error) { return wxa.getAnalysisRetain(getAnalysisWeeklyRetainURL, beginDate, endDate) } @@ -166,12 +166,12 @@ func (wxa *MiniProgram) GetAnalysisDailyVisitTrend(beginDate, endDate string) (r return wxa.getAnalysisVisitTrend(getAnalysisDailyVisitTrendURL, beginDate, endDate) } -// GetAnalysisMonthlyVisitTrend 获取用户访问小程序数据日趋势 +// GetAnalysisMonthlyVisitTrend 获取用户访问小程序数据月趋势 func (wxa *MiniProgram) GetAnalysisMonthlyVisitTrend(beginDate, endDate string) (result ResAnalysisVisitTrend, err error) { return wxa.getAnalysisVisitTrend(getAnalysisMonthlyVisitTrendURL, beginDate, endDate) } -// GetAnalysisWeeklyVisitTrend 获取用户访问小程序数据日趋势 +// GetAnalysisWeeklyVisitTrend 获取用户访问小程序数据周趋势 func (wxa *MiniProgram) GetAnalysisWeeklyVisitTrend(beginDate, endDate string) (result ResAnalysisVisitTrend, err error) { return wxa.getAnalysisVisitTrend(getAnalysisWeeklyVisitTrendURL, beginDate, endDate) } From 823c54fda5b838940e29b021f74a8ab4f18a89c1 Mon Sep 17 00:00:00 2001 From: JefferyWang Date: Wed, 10 Apr 2019 17:26:21 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=B0=8F=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F=E7=94=A8=E6=88=B7=E6=95=B0=E6=8D=AE=E8=A7=A3=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- miniprogram/decrypt.go | 93 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 miniprogram/decrypt.go diff --git a/miniprogram/decrypt.go b/miniprogram/decrypt.go new file mode 100644 index 0000000..6897368 --- /dev/null +++ b/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 +}