mirror of
https://github.com/silenceper/wechat.git
synced 2026-02-15 02:02:27 +08:00
企业微信内部开发API:新增获取客户列表,客户详情,并群发消息
This commit is contained in:
90
work/externalcontact/add_msg_template.go
Normal file
90
work/externalcontact/add_msg_template.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package externalcontact
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/silenceper/wechat/v2/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
addMsgTemplateUrl = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_msg_template"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChatType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ChatTypeSingle ChatType = "single"
|
||||||
|
ChatTypeGroup ChatType = "group"
|
||||||
|
)
|
||||||
|
|
||||||
|
//ReqMessage 企业群发参数
|
||||||
|
type ReqMessage struct {
|
||||||
|
ChatType ChatType `json:"chat_type"` //群发任务的类型,默认为single,表示发送给客户,group表示发送给客户群
|
||||||
|
ExternalUserid []string `json:"external_userid"` // 客户的外部联系人id列表,仅在chat_type为single时有效,不可与sender同时为空,最多可传入1万个客户
|
||||||
|
Sender string `json:"sender"` //发送企业群发消息的成员userid,当类型为发送给客户群时必填
|
||||||
|
Text struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
} `json:"text"`
|
||||||
|
Attachments []struct {
|
||||||
|
Msgtype string `json:"msgtype"`
|
||||||
|
Image MsgImage `json:"image"`
|
||||||
|
Link MsgLink `json:"link"`
|
||||||
|
Miniprogram MsgMiniprogram `json:"miniprogram"`
|
||||||
|
Video MsgVideo `json:"video"`
|
||||||
|
File MsgFile `json:"file"`
|
||||||
|
} `json:"attachments"`
|
||||||
|
}
|
||||||
|
type MsgImage struct {
|
||||||
|
MediaId string `json:"media_id"`
|
||||||
|
PicUrl string `json:"pic_url"`
|
||||||
|
}
|
||||||
|
type MsgLink struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Picurl string `json:"picurl"`
|
||||||
|
Desc string `json:"desc"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
}
|
||||||
|
type MsgMiniprogram struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
PicMediaId string `json:"pic_media_id"`
|
||||||
|
Appid string `json:"appid"`
|
||||||
|
Page string `json:"page"`
|
||||||
|
}
|
||||||
|
type MsgVideo struct {
|
||||||
|
MediaId string `json:"media_id"`
|
||||||
|
}
|
||||||
|
type MsgFile struct {
|
||||||
|
MediaId string `json:"media_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type resTemplateSend struct {
|
||||||
|
util.CommonError
|
||||||
|
FailList string `json:"fail_list"`
|
||||||
|
MsgID int64 `json:"msgid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//Send 发送应用消息
|
||||||
|
func (tpl *Client) Send(msg *ReqMessage) (msgID int64, err error) {
|
||||||
|
var accessToken string
|
||||||
|
accessToken, err = tpl.ctx.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uri := fmt.Sprintf("%s?access_token=%s", addMsgTemplateUrl, accessToken)
|
||||||
|
var response []byte
|
||||||
|
response, err = util.PostJSON(uri, msg)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var result resTemplateSend
|
||||||
|
err = json.Unmarshal(response, &result)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if result.ErrCode != 0 {
|
||||||
|
err = fmt.Errorf("template msg send error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msgID = result.MsgID
|
||||||
|
return
|
||||||
|
}
|
||||||
44
work/externalcontact/client.go
Normal file
44
work/externalcontact/client.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package externalcontact
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/silenceper/wechat/v2/cache"
|
||||||
|
"github.com/silenceper/wechat/v2/credential"
|
||||||
|
"github.com/silenceper/wechat/v2/work/config"
|
||||||
|
"github.com/silenceper/wechat/v2/work/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client 企业微信客户联系实例
|
||||||
|
type Client struct {
|
||||||
|
corpID string // 企业ID:企业开通的每个微信客服,都对应唯一的企业ID,企业可在微信客服管理后台的企业信息处查看
|
||||||
|
secret string // Secret是微信客服用于校验开发者身份的访问密钥,企业成功注册微信客服后,可在「微信客服管理后台-开发配置」处获取
|
||||||
|
token string // 用于生成签名校验回调请求的合法性
|
||||||
|
encodingAESKey string // 回调消息加解密参数是AES密钥的Base64编码,用于解密回调消息内容对应的密文
|
||||||
|
cache cache.Cache
|
||||||
|
ctx *context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient 初始化企业微信客户联系实例
|
||||||
|
func NewClient(cfg *config.Config) (client *Client, err error) {
|
||||||
|
if cfg.Cache == nil {
|
||||||
|
return nil, errors.New("SDK初始化失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
//初始化 AccessToken Handle
|
||||||
|
defaultAkHandle := credential.NewWorkAccessToken(cfg.CorpID, cfg.CorpSecret, credential.CacheKeyWorkPrefix, cfg.Cache)
|
||||||
|
ctx := &context.Context{
|
||||||
|
Config: cfg,
|
||||||
|
AccessTokenHandle: defaultAkHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
client = &Client{
|
||||||
|
corpID: cfg.CorpID,
|
||||||
|
secret: cfg.CorpSecret,
|
||||||
|
token: cfg.Token,
|
||||||
|
encodingAESKey: cfg.EncodingAESKey,
|
||||||
|
cache: cfg.Cache,
|
||||||
|
ctx: ctx,
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
211
work/externalcontact/user.go
Normal file
211
work/externalcontact/user.go
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
package externalcontact
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/silenceper/wechat/v2/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
listUrl = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list"
|
||||||
|
getUrl = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get"
|
||||||
|
getByUserBatchUrl = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/batch/get_by_user"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReqGetByUser struct {
|
||||||
|
UseridList []string `json:"userid_list"`
|
||||||
|
Cursor string `json:"cursor"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
}
|
||||||
|
type OneUser struct {
|
||||||
|
util.CommonError
|
||||||
|
ExternalContact ExternalContact `json:"external_contact"`
|
||||||
|
FollowUser []FollowInfo `json:"follow_user"` //注意,仅获取单个客户详情的时候这里返回的是跟进记录列表
|
||||||
|
NextCursor string `json:"next_cursor"`
|
||||||
|
}
|
||||||
|
type resUserList struct {
|
||||||
|
util.CommonError
|
||||||
|
ExternalContactList []UserInfo `json:"external_contact_list"`
|
||||||
|
NextCursor string `json:"next_cursor"`
|
||||||
|
}
|
||||||
|
type resUserids struct {
|
||||||
|
util.CommonError
|
||||||
|
ExternalUserid []string `json:"external_userid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserInfo struct {
|
||||||
|
ExternalContact ExternalContact `json:"external_contact"`
|
||||||
|
FollowInfo FollowInfo `json:"follow_info"` //企业成员客户跟进信息,可以参考获取客户详情,但标签信息只会返回企业标签和规则组标签的tag_id,个人标签将不再返回
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExternalContact struct {
|
||||||
|
ExternalUserid string `json:"external_userid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Position string `json:"position"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
CorpName string `json:"corp_name"`
|
||||||
|
CorpFullName string `json:"corp_full_name"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
Gender int `json:"gender"`
|
||||||
|
Unionid string `json:"unionid"`
|
||||||
|
ExternalProfile struct {
|
||||||
|
ExternalAttr []struct {
|
||||||
|
Type int `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Text struct {
|
||||||
|
Value string `json:"value"`
|
||||||
|
} `json:"text,omitempty"`
|
||||||
|
Web struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
} `json:"web,omitempty"`
|
||||||
|
Miniprogram struct {
|
||||||
|
Appid string `json:"appid"`
|
||||||
|
Pagepath string `json:"pagepath"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
} `json:"miniprogram,omitempty"`
|
||||||
|
} `json:"external_attr"`
|
||||||
|
} `json:"external_profile,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FollowInfo struct {
|
||||||
|
Userid string `json:"userid"`
|
||||||
|
Remark string `json:"remark"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Createtime int `json:"createtime"`
|
||||||
|
TagId []string `json:"tag_id"` //批量获取时才有
|
||||||
|
Tags []struct {
|
||||||
|
GroupName string `json:"group_name"`
|
||||||
|
TagName string `json:"tag_name"`
|
||||||
|
TagId string `json:"tag_id,omitempty"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
} `json:"tags,omitempty"` //单独获取时才有
|
||||||
|
RemarkCorpName string `json:"remark_corp_name,omitempty"`
|
||||||
|
RemarkMobiles []string `json:"remark_mobiles,omitempty"`
|
||||||
|
OperUserid string `json:"oper_userid"`
|
||||||
|
AddWay int `json:"add_way"`
|
||||||
|
State string `json:"state,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//GetUseridList 获取我的客户列表
|
||||||
|
func (tpl *Client) GetUseridList(myUserid string) (externalUserid []string, err error) {
|
||||||
|
var accessToken string
|
||||||
|
accessToken, err = tpl.ctx.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uri := fmt.Sprintf("%s?access_token=%s&userid=%s", listUrl, accessToken, myUserid)
|
||||||
|
var response []byte
|
||||||
|
response, err = util.HTTPGet(uri)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var result resUserids
|
||||||
|
err = json.Unmarshal(response, &result)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if result.ErrCode != 0 {
|
||||||
|
err = fmt.Errorf("template msg send error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
externalUserid = result.ExternalUserid
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//GetUseridList 获取我的全部客户列表及详情
|
||||||
|
func (tpl *Client) GetQyUserInfoList(qyUserid []string) ([]UserInfo, error) {
|
||||||
|
var userInfoList []UserInfo
|
||||||
|
var req ReqGetByUser
|
||||||
|
req.UseridList = qyUserid
|
||||||
|
req.Limit = 100
|
||||||
|
for {
|
||||||
|
userInfoPage, resCursor, err := tpl.GetUserInfoListByUserIds(req)
|
||||||
|
if err != nil {
|
||||||
|
return userInfoList, err
|
||||||
|
}
|
||||||
|
userInfoList = append(userInfoList, userInfoPage...)
|
||||||
|
if resCursor != "" {
|
||||||
|
req.Cursor = resCursor
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return userInfoList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//GetUserInfoAndAllFollow 获取客户详情以及全部跟进记录
|
||||||
|
func (tpl *Client) GetUserInfoAndAllFollow(userid string) (OneUser, error) {
|
||||||
|
var result, res OneUser
|
||||||
|
var err error
|
||||||
|
var cursor string
|
||||||
|
for {
|
||||||
|
res, err = tpl.GetUserInfo(userid, cursor)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
result.FollowUser = append(result.FollowUser, res.FollowUser...)
|
||||||
|
result.ExternalContact = res.ExternalContact
|
||||||
|
if res.NextCursor != "" {
|
||||||
|
cursor = res.NextCursor
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//GetUserInfo 获取客户详情
|
||||||
|
func (tpl *Client) GetUserInfo(externalUserid string, cursor ...string) (result OneUser, err error) {
|
||||||
|
var accessToken string
|
||||||
|
accessToken, err = tpl.ctx.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var page = ""
|
||||||
|
if len(cursor) > 0 {
|
||||||
|
page = cursor[0]
|
||||||
|
}
|
||||||
|
uri := fmt.Sprintf("%s?access_token=%s&external_userid=%s&cursor=%s", getUrl, accessToken, externalUserid, page)
|
||||||
|
var response []byte
|
||||||
|
response, err = util.HTTPGet(uri)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(response, &result)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if result.ErrCode != 0 {
|
||||||
|
err = fmt.Errorf("template msg send error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//GetUserInfoListByUserId 批量获取客户详情
|
||||||
|
func (tpl *Client) GetUserInfoListByUserIds(req ReqGetByUser) (userList []UserInfo, nextCursor string, err error) {
|
||||||
|
var accessToken string
|
||||||
|
accessToken, err = tpl.ctx.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uri := fmt.Sprintf("%s?access_token=%s", getByUserBatchUrl, accessToken)
|
||||||
|
var response []byte
|
||||||
|
response, err = util.PostJSON(uri, req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var result resUserList
|
||||||
|
err = json.Unmarshal(response, &result)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if result.ErrCode != 0 {
|
||||||
|
err = fmt.Errorf("template msg send error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userList = result.ExternalContactList
|
||||||
|
nextCursor = result.NextCursor
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/silenceper/wechat/v2/credential"
|
"github.com/silenceper/wechat/v2/credential"
|
||||||
"github.com/silenceper/wechat/v2/work/config"
|
"github.com/silenceper/wechat/v2/work/config"
|
||||||
"github.com/silenceper/wechat/v2/work/context"
|
"github.com/silenceper/wechat/v2/work/context"
|
||||||
|
"github.com/silenceper/wechat/v2/work/externalcontact"
|
||||||
"github.com/silenceper/wechat/v2/work/kf"
|
"github.com/silenceper/wechat/v2/work/kf"
|
||||||
"github.com/silenceper/wechat/v2/work/msgaudit"
|
"github.com/silenceper/wechat/v2/work/msgaudit"
|
||||||
"github.com/silenceper/wechat/v2/work/oauth"
|
"github.com/silenceper/wechat/v2/work/oauth"
|
||||||
@@ -65,3 +66,8 @@ func (wk *Work) GetUser() *user.User {
|
|||||||
func (wk *Work) GetCalendar() *tools.Calendar {
|
func (wk *Work) GetCalendar() *tools.Calendar {
|
||||||
return tools.NewCalendar(wk.ctx)
|
return tools.NewCalendar(wk.ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//GetExternalContact 客户联系
|
||||||
|
func (wk *Work) GetExternalContact() (*externalcontact.Client, error) {
|
||||||
|
return externalcontact.NewClient(wk.ctx.Config)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user