Sfoglia il codice sorgente

客服管理功能添加 (#600)

Wangrong 3 anni fa
parent
commit
37f9e981d6

+ 8 - 6
doc/api/officialaccount.md

@@ -35,12 +35,14 @@
 
 | 名称             | 请求方式  | URL                                    | 是否已实现 | 使用方法 |
 | ---------------- | --------- | -------------------------------------- | ---------- | -------- |
-| 获取客服基本信息 | GET       | /cgi-bin/customservice/getkflist       | NO         |          |
-| 添加客服帐号     | POST      | /customservice/kfaccount/add           | NO         |          |
-| 邀请绑定客服帐号 | POST      | /customservice/kfaccount/inviteworker  | NO         |          |
-| 设置客服信息     | POST      | /customservice/kfaccount/update        | NO         |          |
-| 上传客服头像     | POST/FORM | /customservice/kfaccount/uploadheadimg | NO         |          |
-| 删除客服帐号     | GET       | /customservice/kfaccount/del           | NO         |          |
+| 获取客服基本信息 | GET       | /cgi-bin/customservice/getkflist       | YES       | (csm *Manager) List |
+| 添加客服帐号     | POST      | /customservice/kfaccount/add           | YES       | (csm *Manager) Add |
+| 邀请绑定客服帐号 | POST      | /customservice/kfaccount/inviteworker  | YES       | (csm *Manager) InviteBind |
+| 设置客服信息     | POST      | /customservice/kfaccount/update        | YES       | (csm *Manager) Update |
+| 上传客服头像     | POST/FORM | /customservice/kfaccount/uploadheadimg | YES       | (csm *Manager) UploadHeadImg |
+| 删除客服帐号     | POST      | /customservice/kfaccount/del           | YES       | (csm *Manager) Delete |
+| 获取在线客服     | POST      |  /cgi-bin/customservice/getonlinekflist| YES       | (csm *Manager) OnlineList |
+| 下发客服输入状态 | POST      |  /cgi-bin/message/custom/typing        | YES       | (csm *Manager) SendTypingStatus |
 
 #### 会话控制
 

+ 253 - 0
officialaccount/customerservice/manager.go

@@ -0,0 +1,253 @@
+package customerservice
+
+import (
+	"fmt"
+
+	"github.com/silenceper/wechat/v2/officialaccount/context"
+	"github.com/silenceper/wechat/v2/util"
+)
+
+// TypingStatus 输入状态类型
+type TypingStatus string
+
+const (
+	customerServiceListURL       = "https://api.weixin.qq.com/cgi-bin/customservice/getkflist"
+	customerServiceOnlineListURL = "https://api.weixin.qq.com/cgi-bin/customservice/getonlinekflist"
+	customerServiceAddURL        = "https://api.weixin.qq.com/customservice/kfaccount/add"
+	customerServiceUpdateURL     = "https://api.weixin.qq.com/customservice/kfaccount/update"
+	customerServiceDeleteURL     = "https://api.weixin.qq.com/customservice/kfaccount/del"
+	customerServiceInviteURL     = "https://api.weixin.qq.com/customservice/kfaccount/inviteworker"
+	customerServiceUploadHeadImg = "https://api.weixin.qq.com/customservice/kfaccount/uploadheadimg"
+	customerServiceTypingURL     = "https://api.weixin.qq.com/cgi-bin/message/custom/typing"
+)
+
+const (
+	// Typing 表示正在输入状态
+	Typing TypingStatus = "Typing"
+	// CancelTyping 表示取消正在输入状态
+	CancelTyping TypingStatus = "CancelTyping"
+)
+
+// Manager 客服管理者,可以管理客服
+type Manager struct {
+	*context.Context
+}
+
+// NewCustomerServiceManager 实例化客服管理
+func NewCustomerServiceManager(ctx *context.Context) *Manager {
+	csm := new(Manager)
+	csm.Context = ctx
+	return csm
+}
+
+// KeFuInfo 客服基本信息
+type KeFuInfo struct {
+	KfAccount     string `json:"kf_account"`         // 完整客服帐号,格式为:帐号前缀@公众号微信号
+	KfNick        string `json:"kf_nick"`            // 客服昵称
+	KfID          int    `json:"kf_id"`              // 客服编号
+	KfHeadImgURL  string `json:"kf_headimgurl"`      // 客服头像
+	KfWX          string `json:"kf_wx"`              // 如果客服帐号已绑定了客服人员微信号, 则此处显示微信号
+	InviteWX      string `json:"invite_wx"`          // 如果客服帐号尚未绑定微信号,但是已经发起了一个绑定邀请, 则此处显示绑定邀请的微信号
+	InviteExpTime int    `json:"invite_expire_time"` // 如果客服帐号尚未绑定微信号,但是已经发起过一个绑定邀请, 邀请的过期时间,为unix 时间戳
+	InviteStatus  string `json:"invite_status"`      // 邀请的状态,有等待确认“waiting”,被拒绝“rejected”, 过期“expired”
+}
+
+type resKeFuList struct {
+	util.CommonError
+	KfList []*KeFuInfo `json:"kf_list"`
+}
+
+// List 获取所有客服基本信息
+func (csm *Manager) List() (customerServiceList []*KeFuInfo, err error) {
+	var accessToken string
+	accessToken, err = csm.GetAccessToken()
+	if err != nil {
+		return
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", customerServiceListURL, accessToken)
+	var response []byte
+	response, err = util.HTTPGet(uri)
+	if err != nil {
+		return
+	}
+	var res resKeFuList
+	err = util.DecodeWithError(response, &res, "ListCustomerService")
+	if err != nil {
+		return
+	}
+	customerServiceList = res.KfList
+	return
+}
+
+// KeFuOnlineInfo 客服在线信息
+type KeFuOnlineInfo struct {
+	KfAccount    string `json:"kf_account"`
+	Status       int    `json:"status"`
+	KfID         int    `json:"kf_id"`
+	AcceptedCase int    `json:"accepted_case"`
+}
+
+type resKeFuOnlineList struct {
+	util.CommonError
+	KfOnlineList []*KeFuOnlineInfo `json:"kf_online_list"`
+}
+
+// OnlineList 获取在线客服列表
+func (csm *Manager) OnlineList() (customerServiceOnlineList []*KeFuOnlineInfo, err error) {
+	var accessToken string
+	accessToken, err = csm.GetAccessToken()
+	if err != nil {
+		return
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", customerServiceOnlineListURL, accessToken)
+	var response []byte
+	response, err = util.HTTPGet(uri)
+	if err != nil {
+		return
+	}
+	var res resKeFuOnlineList
+	err = util.DecodeWithError(response, &res, "ListOnlineCustomerService")
+	if err != nil {
+		return
+	}
+	customerServiceOnlineList = res.KfOnlineList
+	return
+}
+
+// Add 添加客服账号
+func (csm *Manager) Add(kfAccount, nickName string) (err error) {
+	// kfAccount:完整客服帐号,格式为:帐号前缀@公众号微信号,帐号前缀最多10个字符,必须是英文、数字字符或者下划线,后缀为公众号微信号,长度不超过30个字符
+	// nickName:客服昵称,最长16个字
+	// 参数此处均不做校验
+	var accessToken string
+	accessToken, err = csm.GetAccessToken()
+	if err != nil {
+		return
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", customerServiceAddURL, accessToken)
+	data := struct {
+		KfAccount string `json:"kf_account"`
+		NickName  string `json:"nickname"`
+	}{
+		KfAccount: kfAccount,
+		NickName:  nickName,
+	}
+	var response []byte
+	response, err = util.PostJSON(uri, data)
+	if err != nil {
+		return
+	}
+	err = util.DecodeWithCommonError(response, "AddCustomerService")
+	return
+}
+
+// Update 修改客服账号
+func (csm *Manager) Update(kfAccount, nickName string) (err error) {
+	var accessToken string
+	accessToken, err = csm.GetAccessToken()
+	if err != nil {
+		return
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", customerServiceUpdateURL, accessToken)
+	data := struct {
+		KfAccount string `json:"kf_account"`
+		NickName  string `json:"nickname"`
+	}{
+		KfAccount: kfAccount,
+		NickName:  nickName,
+	}
+	var response []byte
+	response, err = util.PostJSON(uri, data)
+	if err != nil {
+		return
+	}
+	err = util.DecodeWithCommonError(response, "UpdateCustomerService")
+	return
+}
+
+// Delete 删除客服帐号
+func (csm *Manager) Delete(kfAccount string) (err error) {
+	var accessToken string
+	accessToken, err = csm.GetAccessToken()
+	if err != nil {
+		return
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", customerServiceDeleteURL, accessToken)
+	data := struct {
+		KfAccount string `json:"kf_account"`
+	}{
+		KfAccount: kfAccount,
+	}
+	var response []byte
+	response, err = util.PostJSON(uri, data)
+	if err != nil {
+		return
+	}
+	err = util.DecodeWithCommonError(response, "DeleteCustomerService")
+	return
+}
+
+// InviteBind 邀请绑定客服帐号和微信号
+func (csm *Manager) InviteBind(kfAccount, inviteWX string) (err error) {
+	var accessToken string
+	accessToken, err = csm.GetAccessToken()
+	if err != nil {
+		return
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", customerServiceInviteURL, accessToken)
+	data := struct {
+		KfAccount string `json:"kf_account"`
+		InviteWX  string `json:"invite_wx"`
+	}{
+		KfAccount: kfAccount,
+		InviteWX:  inviteWX,
+	}
+	var response []byte
+	response, err = util.PostJSON(uri, data)
+	if err != nil {
+		return
+	}
+	err = util.DecodeWithCommonError(response, "InviteBindCustomerService")
+	return
+}
+
+// UploadHeadImg 上传客服头像
+func (csm *Manager) UploadHeadImg(kfAccount, fileName string) (err error) {
+	var accessToken string
+	accessToken, err = csm.GetAccessToken()
+	if err != nil {
+		return
+	}
+	uri := fmt.Sprintf("%s?access_token=%s&kf_account=%s", customerServiceUploadHeadImg, accessToken, kfAccount)
+	var response []byte
+	response, err = util.PostFile("media", fileName, uri)
+	if err != nil {
+		return
+	}
+	err = util.DecodeWithCommonError(response, "UploadCustomerServiceHeadImg")
+	return
+}
+
+//SendTypingStatus 下发客服输入状态给用户
+func (csm *Manager) SendTypingStatus(openid string, cmd TypingStatus) (err error) {
+	var accessToken string
+	accessToken, err = csm.GetAccessToken()
+	if err != nil {
+		return
+	}
+	uri := fmt.Sprintf("%s?access_token=%s", customerServiceTypingURL, accessToken)
+	data := struct {
+		ToUser  string `json:"touser"`
+		Command string `json:"command"`
+	}{
+		ToUser:  openid,
+		Command: string(cmd),
+	}
+	var response []byte
+	response, err = util.PostJSON(uri, data)
+	if err != nil {
+		return
+	}
+	err = util.DecodeWithCommonError(response, "SendTypingStatus")
+	return
+}

+ 6 - 0
officialaccount/officialaccount.go

@@ -14,6 +14,7 @@ import (
 	"github.com/silenceper/wechat/v2/officialaccount/broadcast"
 	"github.com/silenceper/wechat/v2/officialaccount/config"
 	"github.com/silenceper/wechat/v2/officialaccount/context"
+	"github.com/silenceper/wechat/v2/officialaccount/customerservice"
 	"github.com/silenceper/wechat/v2/officialaccount/device"
 	"github.com/silenceper/wechat/v2/officialaccount/js"
 	"github.com/silenceper/wechat/v2/officialaccount/material"
@@ -197,3 +198,8 @@ func (officialAccount *OfficialAccount) GetSubscribe() *message.Subscribe {
 	}
 	return officialAccount.subscribeMsg
 }
+
+// GetCustomerServiceManager 客服管理
+func (officialAccount *OfficialAccount) GetCustomerServiceManager() *customerservice.Manager {
+	return customerservice.NewCustomerServiceManager(officialAccount.ctx)
+}

+ 9 - 0
util/error.go

@@ -17,6 +17,15 @@ func (c *CommonError) Error() string {
 	return fmt.Sprintf("%s Error , errcode=%d , errmsg=%s", c.apiName, c.ErrCode, c.ErrMsg)
 }
 
+// NewCommonError 新建CommonError错误,对于无errcode和errmsg的返回也可以返回该通用错误
+func NewCommonError(apiName string, code int64, msg string) *CommonError {
+	return &CommonError{
+		apiName: apiName,
+		ErrCode: code,
+		ErrMsg:  msg,
+	}
+}
+
 // DecodeWithCommonError 将返回值按照CommonError解析
 func DecodeWithCommonError(response []byte, apiName string) (err error) {
 	var commError CommonError