diff --git a/minigame/README.md b/minigame/README.md index a560fb7..b8efbe8 100644 --- a/minigame/README.md +++ b/minigame/README.md @@ -2,4 +2,7 @@ [官方文档](https://developers.weixin.qq.com/minigame/dev/api-backend/) -## 快速入门 \ No newline at end of file +## 快速入门 +```go + +``` \ No newline at end of file diff --git a/miniprogram/README.md b/miniprogram/README.md index 0187308..f5424a5 100644 --- a/miniprogram/README.md +++ b/miniprogram/README.md @@ -2,4 +2,19 @@ [官方文档](https://developers.weixin.qq.com/miniprogram/dev/framework/) + +## 包说明 +- analysis 数据分析相关API + ## 快速入门 +```go +wc := wechat.NewWechat() +memory := cache.NewMemory() +cfg := &miniConfig.Config{ + AppID: "xxx", + AppSecret: "xxx", + Cache: memory, +} +miniprogram := wc.GetMiniProgram(cfg) +miniprogram.GetAnalysis().GetAnalysisDailyRetain() +``` \ No newline at end of file diff --git a/officialaccount/message/message.go b/officialaccount/message/message.go index 87ee8c9..991658d 100644 --- a/officialaccount/message/message.go +++ b/officialaccount/message/message.go @@ -72,6 +72,7 @@ const ( ) const ( + //微信开放平台需要用到 // InfoTypeVerifyTicket 返回ticket InfoTypeVerifyTicket InfoType = "component_verify_ticket" // InfoTypeAuthorized 授权 diff --git a/officialaccount/officialaccount.go b/officialaccount/officialaccount.go index db88761..6651bb6 100644 --- a/officialaccount/officialaccount.go +++ b/officialaccount/officialaccount.go @@ -24,9 +24,9 @@ type OfficialAccount struct { //NewOfficialAccount 实例化公众号API func NewOfficialAccount(cfg *config.Config) *OfficialAccount { - if cfg.Cache == nil { - panic("cache未设置") - } + //if cfg.Cache == nil { + // panic("cache未设置") + //} ctx := &context.Context{ Config: cfg, } diff --git a/openplatform/README.md b/openplatform/README.md index 82ab97b..3fd879c 100644 --- a/openplatform/README.md +++ b/openplatform/README.md @@ -2,4 +2,52 @@ [官方文档](https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Third_party_platform_appid.html) -## 快速入门 \ No newline at end of file +## 快速入门 + +```go +wc := wechat.NewWechat() +memory := cache.NewMemory() +cfg := &openplatform.Config{ + AppID: "xxx", + AppSecret: "xxx", + Token: "xxx", + EncodingAESKey: "xxx", + Cache: memory, +} + +//授权的第三方公众号的appID +appID := "xxx" +openPlatform := wc.GetOpenPlatform(cfg) +officialAccount := openPlatform.GetOfficialAccount(appID) + +// 传入request和responseWriter +server := officialAccount.GetServer(req, rw) +//设置接收消息的处理方法 +server.SetMessageHandler(func(msg message.MixMessage) *message.Reply { + if msg.InfoType == message.InfoTypeVerifyTicket { + componentVerifyTicket, err := openPlatform.SetComponentAccessToken(msg.ComponentVerifyTicket) + if err != nil { + log.Println(err) + return nil + } + //debug + fmt.Println(componentVerifyTicket) + rw.Write([]byte("success")) + return nil + } + //handle other message + // + + + return nil +}) + +//处理消息接收以及回复 +err := server.Serve() +if err != nil { + fmt.Println(err) + return +} +//发送回复的消息 +server.Send() +``` \ No newline at end of file diff --git a/openplatform/config/config.go b/openplatform/config/config.go new file mode 100644 index 0000000..564d383 --- /dev/null +++ b/openplatform/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 +} diff --git a/openplatform/context/accessToken.go b/openplatform/context/accessToken.go new file mode 100644 index 0000000..480df57 --- /dev/null +++ b/openplatform/context/accessToken.go @@ -0,0 +1,224 @@ +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 + if err := ctx.Cache.Set(accessTokenCacheKey, at.AccessToken, time.Duration(expires)*time.Second); err != nil { + return nil, nil + } + 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 + if err := ctx.Cache.Set(authrTokenKey, ret.AccessToken, time.Minute*80); err != nil { + return nil, err + } + 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 +} diff --git a/openplatform/context/context.go b/openplatform/context/context.go new file mode 100644 index 0000000..229acde --- /dev/null +++ b/openplatform/context/context.go @@ -0,0 +1,10 @@ +package context + +import ( + "github.com/silenceper/wechat/openplatform/config" +) + +// Context struct +type Context struct { + *config.Config +} diff --git a/openplatform/officialaccount/officialaccount.go b/openplatform/officialaccount/officialaccount.go new file mode 100644 index 0000000..86b3644 --- /dev/null +++ b/openplatform/officialaccount/officialaccount.go @@ -0,0 +1,32 @@ +package officialaccount + +import ( + "github.com/silenceper/wechat/officialaccount" + offConfig "github.com/silenceper/wechat/officialaccount/config" + offContext "github.com/silenceper/wechat/officialaccount/context" + opContext "github.com/silenceper/wechat/openplatform/context" +) + +type OfficialAccount struct { + //授权的公众号的appID + appID string + *officialaccount.OfficialAccount + opContext *opContext.Context +} + +//NewOfficialAccount 实例化 +//appID :为授权方公众号 APPID,非开放平台第三方平台 APPID +func NewOfficialAccount(opCtx *opContext.Context, appID string) *OfficialAccount { + officialAccount := officialaccount.NewOfficialAccount(&offConfig.Config{ + AppID: opCtx.AppID, + EncodingAESKey: opCtx.EncodingAESKey, + Token: opCtx.Token, + Cache: opCtx.Cache, + }) + //设置获取access_token的函数 + officialAccount.GetContext().SetGetAccessTokenFunc(func(offCtx *offContext.Context) (accessToken string, err error) { + // 获取授权方的access_token + return opCtx.GetAuthrAccessToken(appID) + }) + return &OfficialAccount{appID: appID, OfficialAccount: officialAccount} +} diff --git a/openplatform/openplatform.go b/openplatform/openplatform.go new file mode 100644 index 0000000..9a7aba3 --- /dev/null +++ b/openplatform/openplatform.go @@ -0,0 +1,28 @@ +package openplatform + +import ( + "github.com/silenceper/wechat/openplatform/config" + "github.com/silenceper/wechat/openplatform/context" + "github.com/silenceper/wechat/openplatform/officialaccount" +) + +//OpenPlatform 微信开放平台相关api +type OpenPlatform struct { + *context.Context +} + +//NewOpenPlatform new openplatform +func NewOpenPlatform(cfg *config.Config) *OpenPlatform { + if cfg.Cache == nil { + panic("cache 未设置") + } + ctx := &context.Context{ + Config: cfg, + } + return &OpenPlatform{ctx} +} + +//GetOfficialAccount 公众号代处理 +func (openPlatform *OpenPlatform) GetOfficialAccount(appID string) *officialaccount.OfficialAccount { + return officialaccount.NewOfficialAccount(openPlatform.Context, appID) +} diff --git a/wechat.go b/wechat.go index fd3ae57..c242e89 100644 --- a/wechat.go +++ b/wechat.go @@ -1,17 +1,20 @@ package wechat import ( + "github.com/silenceper/wechat/cache" "github.com/silenceper/wechat/miniprogram" 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/openplatform" + opConfig "github.com/silenceper/wechat/openplatform/config" "github.com/silenceper/wechat/pay" + payConfig "github.com/silenceper/wechat/pay/config" ) // Wechat struct type Wechat struct { + cache cache.Cache } // Config for user @@ -26,13 +29,24 @@ func NewWechat() *Wechat { return &Wechat{} } +//SetCache 设置cache +func (wc *Wechat) SetCache(cahce cache.Cache) { + wc.cache = cahce +} + //GetOfficialAccount 获取微信公众号实例 func (wc *Wechat) GetOfficialAccount(cfg *offConfig.Config) *officialaccount.OfficialAccount { + if cfg.Cache == nil { + cfg.Cache = wc.cache + } return officialaccount.NewOfficialAccount(cfg) } // GetMiniProgram 获取小程序的实例 func (wc *Wechat) GetMiniProgram(cfg *miniConfig.Config) *miniprogram.MiniProgram { + if cfg.Cache == nil { + cfg.Cache = wc.cache + } return miniprogram.NewMiniProgram(cfg) } @@ -40,3 +54,8 @@ func (wc *Wechat) GetMiniProgram(cfg *miniConfig.Config) *miniprogram.MiniProgra func (wc *Wechat) GetPay(cfg *payConfig.Config) *pay.Pay { return pay.NewPay(cfg) } + +// GetOpenPlatform 获取微信开放平台的实例 +func (wc *Wechat) GetOpenPlatform(cfg *opConfig.Config) *openplatform.OpenPlatform { + return openplatform.NewOpenPlatform(cfg) +}