فهرست منبع

Merge branch 'master' into master

silenceper 7 سال پیش
والد
کامیت
c1f886db25
14فایلهای تغییر یافته به همراه272 افزوده شده و 84 حذف شده
  1. 1 0
      .gitignore
  2. 1 2
      .travis.yml
  3. 1 1
      README.md
  4. 6 1
      cache/redis.go
  5. 76 0
      context/qy_access_token.go
  6. 2 9
      material/material.go
  7. 4 4
      material/media.go
  8. 8 37
      menu/menu.go
  9. 10 9
      message/message.go
  10. 95 0
      oauth/qy_oauth.go
  11. 10 0
      server/server.go
  12. 35 16
      user/user.go
  13. 18 0
      util/error.go
  14. 5 5
      wechat.go

+ 1 - 0
.gitignore

@@ -25,3 +25,4 @@ _testmain.go
 .DS_Store
 .vscode/
 vendor/*/
+.idea/

+ 1 - 2
.travis.yml

@@ -1,11 +1,10 @@
 language: go
 
 go:
+  - 1.11.x
   - 1.10.x
   - 1.9.x
   - 1.8.x
-  - 1.7.x
-  - 1.6.x
 
 services:
   - memcached

+ 1 - 1
README.md

@@ -272,7 +272,7 @@ type Reply struct {
 }
 
 ```
-注意:`retrun nil`表示什么也不做
+注意:`return nil`表示什么也不做
 
 ####  回复文本消息
 ```go

+ 6 - 1
cache/redis.go

@@ -4,7 +4,7 @@ import (
 	"encoding/json"
 	"time"
 
-	"github.com/garyburd/redigo/redis"
+	"github.com/gomodule/redigo/redis"
 )
 
 //Redis redis cache
@@ -45,6 +45,11 @@ func NewRedis(opts *RedisOpts) *Redis {
 	return &Redis{pool}
 }
 
+//SetConn 设置conn
+func (r *Redis) SetConn(conn *redis.Pool) {
+	r.conn = conn
+}
+
 //Get 获取一个值
 func (r *Redis) Get(key string) interface{} {
 	conn := r.conn.Get()

+ 76 - 0
context/qy_access_token.go

@@ -0,0 +1,76 @@
+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
+}

+ 2 - 9
material/material.go

@@ -184,13 +184,6 @@ func (material *Material) DeleteMaterial(mediaID string) error {
 	if err != nil {
 		return err
 	}
-	var resDeleteMaterial util.CommonError
-	err = json.Unmarshal(response, &resDeleteMaterial)
-	if err != nil {
-		return err
-	}
-	if resDeleteMaterial.ErrCode != 0 {
-		return fmt.Errorf("DeleteMaterial error : errcode=%v , errmsg=%v", resDeleteMaterial.ErrCode, resDeleteMaterial.ErrMsg)
-	}
-	return nil
+
+	return util.DecodeWithCommonError(response, "DeleteMaterial")
 }

+ 4 - 4
material/media.go

@@ -31,10 +31,10 @@ const (
 type Media struct {
 	util.CommonError
 
-	Type      MediaType `json:"type"`
-	MediaID   string    `json:"media_id"`
-	ThumbMediaID string `json:"thumb_media_id"`
-	CreatedAt int64     `json:"created_at"`
+	Type         MediaType `json:"type"`
+	MediaID      string    `json:"media_id"`
+	ThumbMediaID string    `json:"thumb_media_id"`
+	CreatedAt    int64     `json:"created_at"`
 }
 
 //MediaUpload 临时素材上传

+ 8 - 37
menu/menu.go

@@ -134,15 +134,8 @@ func (menu *Menu) SetMenu(buttons []*Button) error {
 	if err != nil {
 		return err
 	}
-	var commError util.CommonError
-	err = json.Unmarshal(response, &commError)
-	if err != nil {
-		return err
-	}
-	if commError.ErrCode != 0 {
-		return fmt.Errorf("SetMenu Error , errcode=%d , errmsg=%s", commError.ErrCode, commError.ErrMsg)
-	}
-	return nil
+
+	return util.DecodeWithCommonError(response, "SetMenu")
 }
 
 //GetMenu 获取菜单配置
@@ -180,15 +173,8 @@ func (menu *Menu) DeleteMenu() error {
 	if err != nil {
 		return err
 	}
-	var commError util.CommonError
-	err = json.Unmarshal(response, &commError)
-	if err != nil {
-		return err
-	}
-	if commError.ErrCode != 0 {
-		return fmt.Errorf("GetMenu Error , errcode=%d , errmsg=%s", commError.ErrCode, commError.ErrMsg)
-	}
-	return nil
+
+	return util.DecodeWithCommonError(response, "GetMenu")
 }
 
 //AddConditional 添加个性化菜单
@@ -208,15 +194,8 @@ func (menu *Menu) AddConditional(buttons []*Button, matchRule *MatchRule) error
 	if err != nil {
 		return err
 	}
-	var commError util.CommonError
-	err = json.Unmarshal(response, &commError)
-	if err != nil {
-		return err
-	}
-	if commError.ErrCode != 0 {
-		return fmt.Errorf("AddConditional Error , errcode=%d , errmsg=%s", commError.ErrCode, commError.ErrMsg)
-	}
-	return nil
+
+	return util.DecodeWithCommonError(response, "AddConditional")
 }
 
 //DeleteConditional 删除个性化菜单
@@ -235,15 +214,8 @@ func (menu *Menu) DeleteConditional(menuID int64) error {
 	if err != nil {
 		return err
 	}
-	var commError util.CommonError
-	err = json.Unmarshal(response, &commError)
-	if err != nil {
-		return err
-	}
-	if commError.ErrCode != 0 {
-		return fmt.Errorf("DeleteConditional Error , errcode=%d , errmsg=%s", commError.ErrCode, commError.ErrMsg)
-	}
-	return nil
+
+	return util.DecodeWithCommonError(response, "DeleteConditional")
 }
 
 //MenuTryMatch 菜单匹配
@@ -286,7 +258,6 @@ func (menu *Menu) GetCurrentSelfMenuInfo() (resSelfMenuInfo ResSelfMenuInfo, err
 	if err != nil {
 		return
 	}
-	fmt.Println(string(response))
 	err = json.Unmarshal(response, &resSelfMenuInfo)
 	if err != nil {
 		return

+ 10 - 9
message/message.go

@@ -69,6 +69,7 @@ type MixMessage struct {
 	//基本消息
 	MsgID        int64   `xml:"MsgId"`
 	Content      string  `xml:"Content"`
+	Recognition  string  `xml:"Recognition"`
 	PicURL       string  `xml:"PicUrl"`
 	MediaID      string  `xml:"MediaId"`
 	Format       string  `xml:"Format"`
@@ -82,15 +83,15 @@ type MixMessage struct {
 	URL          string  `xml:"Url"`
 
 	//事件相关
-	Event     EventType `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"`
-	Status    string    `xml:"Status"`
-	SessionFrom string  `xml:"SessionFrom"`
+	Event       EventType `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"`
+	Status      string    `xml:"Status"`
+	SessionFrom string    `xml:"SessionFrom"`
 
 	ScanCodeInfo struct {
 		ScanType   string `xml:"ScanType"`

+ 95 - 0
oauth/qy_oauth.go

@@ -0,0 +1,95 @@
+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
+}

+ 10 - 0
server/server.go

@@ -18,6 +18,8 @@ import (
 type Server struct {
 	*context.Context
 
+	debug bool
+
 	openID string
 
 	messageHandler func(message.MixMessage) *message.Reply
@@ -40,6 +42,11 @@ func NewServer(context *context.Context) *Server {
 	return srv
 }
 
+// SetDebug set debug field
+func (srv *Server) SetDebug(debug bool) {
+	srv.debug = debug
+}
+
 //Serve 处理微信的请求消息
 func (srv *Server) Serve() error {
 	if !srv.Validate() {
@@ -65,6 +72,9 @@ func (srv *Server) Serve() error {
 
 //Validate 校验请求是否合法
 func (srv *Server) Validate() bool {
+	if srv.debug {
+		return true
+	}
 	timestamp := srv.Query("timestamp")
 	nonce := srv.Query("nonce")
 	signature := srv.Query("signature")

+ 35 - 16
user/user.go

@@ -9,7 +9,8 @@ import (
 )
 
 const (
-	userInfoURL = "https://api.weixin.qq.com/cgi-bin/user/info"
+	userInfoURL     = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s&lang=zh_CN"
+	updateRemarkURL = "https://api.weixin.qq.com/cgi-bin/user/info/updateremark?access_token=%s"
 )
 
 //User 用户管理
@@ -28,20 +29,20 @@ func NewUser(context *context.Context) *User {
 type Info struct {
 	util.CommonError
 
-	Subscribe     int32    `json:"subscribe"`
-	OpenID        string   `json:"openid"`
-	Nickname      string   `json:"nickname"`
-	Sex           int32    `json:"sex"`
-	City          string   `json:"city"`
-	Country       string   `json:"country"`
-	Province      string   `json:"province"`
-	Language      string   `json:"language"`
-	Headimgurl    string   `json:"headimgurl"`
-	SubscribeTime int32    `json:"subscribe_time"`
-	UnionID       string   `json:"unionid"`
-	Remark        string   `json:"remark"`
-	GroupID       int32    `json:"groupid"`
-	TagidList     []string `json:"tagid_list"`
+	Subscribe     int32   `json:"subscribe"`
+	OpenID        string  `json:"openid"`
+	Nickname      string  `json:"nickname"`
+	Sex           int32   `json:"sex"`
+	City          string  `json:"city"`
+	Country       string  `json:"country"`
+	Province      string  `json:"province"`
+	Language      string  `json:"language"`
+	Headimgurl    string  `json:"headimgurl"`
+	SubscribeTime int32   `json:"subscribe_time"`
+	UnionID       string  `json:"unionid"`
+	Remark        string  `json:"remark"`
+	GroupID       int32   `json:"groupid"`
+	TagidList     []int32 `json:"tagid_list"`
 }
 
 //GetUserInfo 获取用户基本信息
@@ -52,7 +53,7 @@ func (user *User) GetUserInfo(openID string) (userInfo *Info, err error) {
 		return
 	}
 
-	uri := fmt.Sprintf("%s?access_token=%s&openid=%s&lang=zh_CN", userInfoURL, accessToken, openID)
+	uri := fmt.Sprintf(userInfoURL, accessToken, openID)
 	var response []byte
 	response, err = util.HTTPGet(uri)
 	if err != nil {
@@ -69,3 +70,21 @@ func (user *User) GetUserInfo(openID string) (userInfo *Info, err error) {
 	}
 	return
 }
+
+// UpdateRemark 设置用户备注名
+func (user *User) UpdateRemark(openID, remark string) (err error) {
+	var accessToken string
+	accessToken, err = user.GetAccessToken()
+	if err != nil {
+		return
+	}
+
+	uri := fmt.Sprintf(updateRemarkURL, accessToken)
+	var response []byte
+	response, err = util.PostJSON(uri, map[string]string{"openid": openID, "remark": remark})
+	if err != nil {
+		return
+	}
+
+	return util.DecodeWithCommonError(response, "UpdateRemark")
+}

+ 18 - 0
util/error.go

@@ -1,7 +1,25 @@
 package util
 
+import (
+	"encoding/json"
+	"fmt"
+)
+
 // CommonError 微信返回的通用错误json
 type CommonError struct {
 	ErrCode int64  `json:"errcode"`
 	ErrMsg  string `json:"errmsg"`
 }
+
+// DecodeWithCommonError 将返回值按照CommonError解析
+func DecodeWithCommonError(response []byte, apiName string) (err error) {
+	var commError CommonError
+	err = json.Unmarshal(response, &commError)
+	if err != nil {
+		return
+	}
+	if commError.ErrCode != 0 {
+		return fmt.Errorf("%s Error , errcode=%d , errmsg=%s", apiName, commError.ErrCode, commError.ErrMsg)
+	}
+	return nil
+}

+ 5 - 5
wechat.go

@@ -10,10 +10,10 @@ import (
 	"github.com/silenceper/wechat/material"
 	"github.com/silenceper/wechat/menu"
 	"github.com/silenceper/wechat/oauth"
+	"github.com/silenceper/wechat/pay"
 	"github.com/silenceper/wechat/server"
 	"github.com/silenceper/wechat/template"
 	"github.com/silenceper/wechat/user"
-	"github.com/silenceper/wechat/pay"
 )
 
 // Wechat struct
@@ -27,9 +27,9 @@ type Config struct {
 	AppSecret      string
 	Token          string
 	EncodingAESKey string
-	PayMchID       string  //支付 - 商户 ID
-	PayNotifyURL   string  //支付 - 接受微信支付结果通知的接口地址
-	PayKey         string  //支付 - 商户后台设置的支付 key
+	PayMchID       string //支付 - 商户 ID
+	PayNotifyURL   string //支付 - 接受微信支付结果通知的接口地址
+	PayKey         string //支付 - 商户后台设置的支付 key
 	Cache          cache.Cache
 }
 
@@ -98,4 +98,4 @@ func (wc *Wechat) GetTemplate() *template.Template {
 // GetPay 返回支付消息的实例
 func (wc *Wechat) GetPay() *pay.Pay {
 	return pay.NewPay(wc.Context)
-}
+}