Jelajahi Sumber

增加企业微信 企业内部开发模块 (#418)

* 增加小程序内容安全接口

* 内容安全接口 按照golint规范进行优化

* 内容安全接口 按照golint规范进行优化

* 删除CheckImage中的输出代码

* 小程序内容安全接口

* 小程序内容安全接口

* 小程序内容安全接口
1:修改返回值 改为error异常统一返回

* 增加企业微信 企业内部开发模块
1:授权登录

* 增加企业微信 企业内部开发模块

* 修改参数为小写

* 优化参数格式

Co-authored-by: root <admin@example.com>
从小就很酷 5 tahun lalu
induk
melakukan
c1e4e2c9d0
6 mengubah file dengan 212 tambahan dan 5 penghapusan
  1. 59 5
      credential/default_access_token.go
  2. 7 0
      wechat.go
  3. 14 0
      work/config/config.go
  4. 12 0
      work/context/context.go
  5. 87 0
      work/oauth/oauth.go
  6. 33 0
      work/work.go

+ 59 - 5
credential/default_access_token.go

@@ -12,11 +12,15 @@ import (
 
 const (
 	//AccessTokenURL 获取access_token的接口
-	accessTokenURL = "https://api.weixin.qq.com/cgi-bin/token"
+	accessTokenURL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"
+	//AccessTokenURL 企业微信获取access_token的接口
+	workAccessTokenURL = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s"
 	//CacheKeyOfficialAccountPrefix 微信公众号cache key前缀
 	CacheKeyOfficialAccountPrefix = "gowechat_officialaccount_"
 	//CacheKeyMiniProgramPrefix 小程序cache key前缀
 	CacheKeyMiniProgramPrefix = "gowechat_miniprogram_"
+	//CacheKeyWorkPrefix 企业微信cache key前缀
+	CacheKeyWorkPrefix = "gowechat_work_"
 )
 
 //DefaultAccessToken 默认AccessToken 获取
@@ -65,7 +69,58 @@ func (ak *DefaultAccessToken) GetAccessToken() (accessToken string, err error) {
 
 	//cache失效,从微信服务器获取
 	var resAccessToken ResAccessToken
-	resAccessToken, err = GetTokenFromServer(ak.appID, ak.appSecret)
+	resAccessToken, err = GetTokenFromServer(fmt.Sprintf(accessTokenURL, ak.appID, ak.appSecret))
+	if err != nil {
+		return
+	}
+
+	expires := resAccessToken.ExpiresIn - 1500
+	err = ak.cache.Set(accessTokenCacheKey, resAccessToken.AccessToken, time.Duration(expires)*time.Second)
+	if err != nil {
+		return
+	}
+	accessToken = resAccessToken.AccessToken
+	return
+}
+
+//WorkAccessToken 企业微信AccessToken 获取
+type WorkAccessToken struct {
+	CorpID          string
+	CorpSecret      string
+	cacheKeyPrefix  string
+	cache           cache.Cache
+	accessTokenLock *sync.Mutex
+}
+
+//NewWorkAccessToken new WorkAccessToken
+func NewWorkAccessToken(corpID, corpSecret, cacheKeyPrefix string, cache cache.Cache) AccessTokenHandle {
+	if cache == nil {
+		panic("cache the not exist")
+	}
+	return &WorkAccessToken{
+		CorpID:          corpID,
+		CorpSecret:      corpSecret,
+		cache:           cache,
+		cacheKeyPrefix:  cacheKeyPrefix,
+		accessTokenLock: new(sync.Mutex),
+	}
+}
+
+//GetAccessToken 企业微信获取access_token,先从cache中获取,没有则从服务端获取
+func (ak *WorkAccessToken) GetAccessToken() (accessToken string, err error) {
+	//加上lock,是为了防止在并发获取token时,cache刚好失效,导致从微信服务器上获取到不同token
+	ak.accessTokenLock.Lock()
+	defer ak.accessTokenLock.Unlock()
+	accessTokenCacheKey := fmt.Sprintf("%s_access_token_%s", ak.cacheKeyPrefix, ak.CorpID)
+	val := ak.cache.Get(accessTokenCacheKey)
+	if val != nil {
+		accessToken = val.(string)
+		return
+	}
+
+	//cache失效,从微信服务器获取
+	var resAccessToken ResAccessToken
+	resAccessToken, err = GetTokenFromServer(fmt.Sprintf(workAccessTokenURL, ak.CorpID, ak.CorpSecret))
 	if err != nil {
 		return
 	}
@@ -80,8 +135,7 @@ func (ak *DefaultAccessToken) GetAccessToken() (accessToken string, err error) {
 }
 
 //GetTokenFromServer 强制从微信服务器获取token
-func GetTokenFromServer(appID, appSecret string) (resAccessToken ResAccessToken, err error) {
-	url := fmt.Sprintf("%s?grant_type=client_credential&appid=%s&secret=%s", accessTokenURL, appID, appSecret)
+func GetTokenFromServer(url string) (resAccessToken ResAccessToken, err error) {
 	var body []byte
 	body, err = util.HTTPGet(url)
 	if err != nil {
@@ -91,7 +145,7 @@ func GetTokenFromServer(appID, appSecret string) (resAccessToken ResAccessToken,
 	if err != nil {
 		return
 	}
-	if resAccessToken.ErrMsg != "" {
+	if resAccessToken.ErrCode != 0 {
 		err = fmt.Errorf("get access_token error : errcode=%v , errormsg=%v", resAccessToken.ErrCode, resAccessToken.ErrMsg)
 		return
 	}

+ 7 - 0
wechat.go

@@ -12,6 +12,8 @@ import (
 	openConfig "github.com/silenceper/wechat/v2/openplatform/config"
 	"github.com/silenceper/wechat/v2/pay"
 	payConfig "github.com/silenceper/wechat/v2/pay/config"
+	"github.com/silenceper/wechat/v2/work"
+	workConfig "github.com/silenceper/wechat/v2/work/config"
 	log "github.com/sirupsen/logrus"
 )
 
@@ -67,3 +69,8 @@ func (wc *Wechat) GetPay(cfg *payConfig.Config) *pay.Pay {
 func (wc *Wechat) GetOpenPlatform(cfg *openConfig.Config) *openplatform.OpenPlatform {
 	return openplatform.NewOpenPlatform(cfg)
 }
+
+// GetWork 获取企业微信的实例
+func (wc *Wechat) GetWork(cfg *workConfig.Config) *work.Work {
+	return work.NewWork(cfg)
+}

+ 14 - 0
work/config/config.go

@@ -0,0 +1,14 @@
+// Package config 企业微信config配置
+package config
+
+import (
+	"github.com/silenceper/wechat/v2/cache"
+)
+
+// Config config for 企业微信
+type Config struct {
+	CorpID     string `json:"corp_id"`     // corp_id
+	CorpSecret string `json:"corp_secret"` // corp_secret
+	AgentID    string `json:"agent_id"`    // agent_id
+	Cache      cache.Cache
+}

+ 12 - 0
work/context/context.go

@@ -0,0 +1,12 @@
+package context
+
+import (
+	"github.com/silenceper/wechat/v2/credential"
+	"github.com/silenceper/wechat/v2/work/config"
+)
+
+// Context struct
+type Context struct {
+	*config.Config
+	credential.AccessTokenHandle
+}

+ 87 - 0
work/oauth/oauth.go

@@ -0,0 +1,87 @@
+package oauth
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/url"
+
+	"github.com/silenceper/wechat/v2/util"
+	"github.com/silenceper/wechat/v2/work/context"
+)
+
+//Oauth auth
+type Oauth struct {
+	*context.Context
+}
+
+var (
+	//oauthTargetURL 企业微信内跳转地址
+	oauthTargetURL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect"
+	//oauthUserInfoURL 获取用户信息地址
+	oauthUserInfoURL = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=%s&code=%s"
+	//oauthQrContentTargetURL 构造独立窗口登录二维码
+	oauthQrContentTargetURL = "https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=%s&agentid=%s&redirect_uri=%s&state=%s"
+)
+
+//NewOauth new init oauth
+func NewOauth(ctx *context.Context) *Oauth {
+	return &Oauth{
+		ctx,
+	}
+}
+
+//GetTargetURL 获取授权地址
+func (ctr *Oauth) GetTargetURL(callbackURL string) string {
+	//url encode
+	urlStr := url.QueryEscape(callbackURL)
+	return fmt.Sprintf(
+		oauthTargetURL,
+		ctr.CorpID,
+		urlStr,
+	)
+}
+
+//GetQrContentTargetURL 构造独立窗口登录二维码
+func (ctr *Oauth) GetQrContentTargetURL(callbackURL string) string {
+	//url encode
+	urlStr := url.QueryEscape(callbackURL)
+	return fmt.Sprintf(
+		oauthQrContentTargetURL,
+		ctr.CorpID,
+		ctr.AgentID,
+		urlStr,
+		util.RandomStr(16),
+	)
+}
+
+//ResUserInfo 返回得用户信息
+type ResUserInfo struct {
+	util.CommonError
+	//当用户为企业成员时返回
+	UserID   string `json:"UserId"`
+	DeviceID string `json:"DeviceId"`
+	//非企业成员授权时返回
+	OpenID string `json:"OpenId"`
+}
+
+//UserFromCode 根据code获取用户信息
+func (ctr *Oauth) UserFromCode(code string) (result ResUserInfo, err error) {
+	var accessToken string
+	accessToken, err = ctr.GetAccessToken()
+	if err != nil {
+		return
+	}
+	var response []byte
+	response, err = util.HTTPGet(
+		fmt.Sprintf(oauthUserInfoURL, accessToken, code),
+	)
+	if err != nil {
+		return
+	}
+	err = json.Unmarshal(response, &result)
+	if result.ErrCode != 0 {
+		err = fmt.Errorf("GetUserAccessToken error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
+		return
+	}
+	return
+}

+ 33 - 0
work/work.go

@@ -0,0 +1,33 @@
+package work
+
+import (
+	"github.com/silenceper/wechat/v2/credential"
+	"github.com/silenceper/wechat/v2/work/config"
+	"github.com/silenceper/wechat/v2/work/context"
+	"github.com/silenceper/wechat/v2/work/oauth"
+)
+
+// Work 企业微信
+type Work struct {
+	ctx *context.Context
+}
+
+//NewWork init work
+func NewWork(cfg *config.Config) *Work {
+	defaultAkHandle := credential.NewWorkAccessToken(cfg.CorpID, cfg.CorpSecret, credential.CacheKeyWorkPrefix, cfg.Cache)
+	ctx := &context.Context{
+		Config:            cfg,
+		AccessTokenHandle: defaultAkHandle,
+	}
+	return &Work{ctx: ctx}
+}
+
+//GetContext get Context
+func (wk *Work) GetContext() *context.Context {
+	return wk.ctx
+}
+
+//GetOauth get oauth
+func (wk *Work) GetOauth() *oauth.Oauth {
+	return oauth.NewOauth(wk.ctx)
+}