From 44b09c7c3bdbb0aedaae3fd77496d35459bfa97f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=B9=E6=99=B6?= Date: Wed, 16 Oct 2024 16:11:03 +0800 Subject: [PATCH 1/7] feat(media): add getTempFile api (#801) add getTempFile api --- work/material/media.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/work/material/media.go b/work/material/media.go index b9647adc..c3c8e0c3 100644 --- a/work/material/media.go +++ b/work/material/media.go @@ -14,6 +14,8 @@ const ( uploadTempFile = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s" // uploadAttachment 上传附件资源 uploadAttachment = "https://qyapi.weixin.qq.com/cgi-bin/media/upload_attachment?access_token=%s&media_type=%s&attachment_type=%d" + // getTempFile 获取临时素材 + getTempFile = "https://qyapi.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s" ) // UploadImgResponse 上传图片响应 @@ -148,3 +150,21 @@ func (r *Client) UploadAttachmentFromReader(filename, mediaType string, reader i err = util.DecodeWithError(response, result, "UploadAttachment") return result, err } + +// GetTempFile 获取临时素材 +// @see https://developer.work.weixin.qq.com/document/path/90254 +func (r *Client) GetTempFile(mediaID string) ([]byte, error) { + var ( + accessToken string + err error + ) + if accessToken, err = r.GetAccessToken(); err != nil { + return nil, err + } + url := fmt.Sprintf(getTempFile, accessToken, mediaID) + response, err := util.HTTPGet(url) + if err != nil { + return nil, err + } + return response, nil +} From 990ba6ede9cf61711201ee6b837ee1efc70cf1b9 Mon Sep 17 00:00:00 2001 From: markwang Date: Wed, 16 Oct 2024 16:11:20 +0800 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=E4=BC=81=E4=B8=9A=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1-=E6=8E=A5=E5=BE=85=E4=BA=BA=E5=91=98=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=A2=9E=E5=8A=A0=E9=83=A8=E9=97=A8ID=E5=8F=8A?= =?UTF-8?q?=E5=81=9C=E6=AD=A2=E6=8E=A5=E5=BE=85=E5=AD=90=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=20(#800)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- work/kf/servicer.go | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/work/kf/servicer.go b/work/kf/servicer.go index 5c34efc9..8e64c3ea 100644 --- a/work/kf/servicer.go +++ b/work/kf/servicer.go @@ -18,20 +18,23 @@ const ( // ReceptionistOptions 添加接待人员请求参数 type ReceptionistOptions struct { - OpenKFID string `json:"open_kfid"` // 客服帐号ID - UserIDList []string `json:"userid_list"` // 接待人员userid列表。第三方应用填密文userid,即open_userid 可填充个数:1 ~ 100。超过100个需分批调用。 + OpenKFID string `json:"open_kfid"` // 客服帐号ID + UserIDList []string `json:"userid_list"` // 接待人员userid列表。第三方应用填密文userid,即open_userid 可填充个数:1 ~ 100。超过100个需分批调用。 + DepartmentIDList []int `json:"department_id_list"` // 接待人员部门id列表 可填充个数:0 ~ 100。超过100个需分批调用。 } // ReceptionistSchema 添加接待人员响应内容 type ReceptionistSchema struct { util.CommonError ResultList []struct { - UserID string `json:"userid"` + UserID string `json:"userid"` + DepartmentID int `json:"department_id"` util.CommonError } `json:"result_list"` } // ReceptionistAdd 添加接待人员 +// @see https://developer.work.weixin.qq.com/document/path/94646 func (r *Client) ReceptionistAdd(options ReceptionistOptions) (info ReceptionistSchema, err error) { var ( accessToken string @@ -49,10 +52,11 @@ func (r *Client) ReceptionistAdd(options ReceptionistOptions) (info Receptionist if info.ErrCode != 0 { return info, NewSDKErr(info.ErrCode, info.ErrMsg) } - return info, nil + return } // ReceptionistDel 删除接待人员 +// @see https://developer.work.weixin.qq.com/document/path/94647 func (r *Client) ReceptionistDel(options ReceptionistOptions) (info ReceptionistSchema, err error) { var ( accessToken string @@ -72,19 +76,22 @@ func (r *Client) ReceptionistDel(options ReceptionistOptions) (info Receptionist if info.ErrCode != 0 { return info, NewSDKErr(info.ErrCode, info.ErrMsg) } - return info, nil + return } // ReceptionistListSchema 获取接待人员列表响应内容 type ReceptionistListSchema struct { util.CommonError ReceptionistList []struct { - UserID string `json:"userid"` // 接待人员的userid。第三方应用获取到的为密文userid,即open_userid - Status int `json:"status"` // 接待人员的接待状态。0:接待中,1:停止接待。第三方应用需具有“管理帐号、分配会话和收发消息”权限才可获取 + UserID string `json:"userid"` // 接待人员的userid。第三方应用获取到的为密文userid,即open_userid + Status int `json:"status"` // 接待人员的接待状态。0:接待中,1:停止接待。第三方应用需具有“管理帐号、分配会话和收发消息”权限才可获取 + DepartmentID int `json:"department_id"` // 接待人员部门的id + StopType int `json:"stop_type"` // 接待人员的接待状态为「停止接待」的子类型。0:停止接待,1:暂时挂起 } `json:"servicer_list"` } // ReceptionistList 获取接待人员列表 +// @see https://developer.work.weixin.qq.com/document/path/94645 func (r *Client) ReceptionistList(kfID string) (info ReceptionistListSchema, err error) { var ( accessToken string @@ -104,5 +111,5 @@ func (r *Client) ReceptionistList(kfID string) (info ReceptionistListSchema, err if info.ErrCode != 0 { return info, NewSDKErr(info.ErrCode, info.ErrMsg) } - return info, nil + return } From 3fbe8634d99d21353024aa9da175d823a54a5183 Mon Sep 17 00:00:00 2001 From: CyJaySong <29367599+cyjaysong@users.noreply.github.com> Date: Sat, 23 Nov 2024 18:25:24 +0800 Subject: [PATCH 3/7] =?UTF-8?q?officeaccount=20=E5=92=8C=20miniprogram=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0UseStableAK=20=E9=85=8D=E7=BD=AE=E9=A1=B9=20(?= =?UTF-8?q?#788)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- miniprogram/config/config.go | 1 + miniprogram/miniprogram.go | 8 +++++++- officialaccount/config/config.go | 1 + officialaccount/officialaccount.go | 8 +++++++- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/miniprogram/config/config.go b/miniprogram/config/config.go index fb3e1518..38cbd036 100644 --- a/miniprogram/config/config.go +++ b/miniprogram/config/config.go @@ -14,4 +14,5 @@ type Config struct { Token string `json:"token"` // token EncodingAESKey string `json:"encoding_aes_key"` // EncodingAESKey Cache cache.Cache + UseStableAK bool // use the stable access_token } diff --git a/miniprogram/miniprogram.go b/miniprogram/miniprogram.go index 8adbb00d..c5027382 100644 --- a/miniprogram/miniprogram.go +++ b/miniprogram/miniprogram.go @@ -34,7 +34,13 @@ type MiniProgram struct { // NewMiniProgram 实例化小程序 API func NewMiniProgram(cfg *config.Config) *MiniProgram { - defaultAkHandle := credential.NewDefaultAccessToken(cfg.AppID, cfg.AppSecret, credential.CacheKeyMiniProgramPrefix, cfg.Cache) + var defaultAkHandle credential.AccessTokenContextHandle + const cacheKeyPrefix = credential.CacheKeyMiniProgramPrefix + if cfg.UseStableAK { + defaultAkHandle = credential.NewStableAccessToken(cfg.AppID, cfg.AppSecret, cacheKeyPrefix, cfg.Cache) + } else { + defaultAkHandle = credential.NewDefaultAccessToken(cfg.AppID, cfg.AppSecret, cacheKeyPrefix, cfg.Cache) + } ctx := &context.Context{ Config: cfg, AccessTokenHandle: defaultAkHandle, diff --git a/officialaccount/config/config.go b/officialaccount/config/config.go index b38fccec..409b2bd2 100644 --- a/officialaccount/config/config.go +++ b/officialaccount/config/config.go @@ -11,4 +11,5 @@ type Config struct { Token string `json:"token"` // token EncodingAESKey string `json:"encoding_aes_key"` // EncodingAESKey Cache cache.Cache + UseStableAK bool // use the stable access_token } diff --git a/officialaccount/officialaccount.go b/officialaccount/officialaccount.go index 0c00817b..fcad7bd1 100644 --- a/officialaccount/officialaccount.go +++ b/officialaccount/officialaccount.go @@ -49,7 +49,13 @@ type OfficialAccount struct { // NewOfficialAccount 实例化公众号API func NewOfficialAccount(cfg *config.Config) *OfficialAccount { - defaultAkHandle := credential.NewDefaultAccessToken(cfg.AppID, cfg.AppSecret, credential.CacheKeyOfficialAccountPrefix, cfg.Cache) + var defaultAkHandle credential.AccessTokenContextHandle + const cacheKeyPrefix = credential.CacheKeyOfficialAccountPrefix + if cfg.UseStableAK { + defaultAkHandle = credential.NewStableAccessToken(cfg.AppID, cfg.AppSecret, cacheKeyPrefix, cfg.Cache) + } else { + defaultAkHandle = credential.NewDefaultAccessToken(cfg.AppID, cfg.AppSecret, cacheKeyPrefix, cfg.Cache) + } ctx := &context.Context{ Config: cfg, AccessTokenHandle: defaultAkHandle, From a571bf354692a4f28c678ccb59fcc6b27164bcee Mon Sep 17 00:00:00 2001 From: yangyl12345 Date: Tue, 26 Nov 2024 12:13:49 +0800 Subject: [PATCH 4/7] Update default_access_token.go (#805) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update default_access_token.go 微信获取稳定版token,只有不等于空字符串的情况下才会返回access_token信息,未空的情况,继续调去微信服务 * 微信稳定性获取 access_token 接口,添加防止并发性获取 access_token 和多次微信服务获取代码 --------- Co-authored-by: w_yangyili --- credential/default_access_token.go | 33 ++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/credential/default_access_token.go b/credential/default_access_token.go index 00ad481c..ec88a89d 100644 --- a/credential/default_access_token.go +++ b/credential/default_access_token.go @@ -101,10 +101,11 @@ func (ak *DefaultAccessToken) GetAccessTokenContext(ctx context.Context) (access // 不强制更新access_token,可用于不同环境不同服务而不需要分布式锁以及公用缓存,避免access_token争抢 // https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getStableAccessToken.html type StableAccessToken struct { - appID string - appSecret string - cacheKeyPrefix string - cache cache.Cache + appID string + appSecret string + cacheKeyPrefix string + cache cache.Cache + accessTokenLock *sync.Mutex } // NewStableAccessToken new StableAccessToken @@ -113,10 +114,11 @@ func NewStableAccessToken(appID, appSecret, cacheKeyPrefix string, cache cache.C panic("cache is need") } return &StableAccessToken{ - appID: appID, - appSecret: appSecret, - cache: cache, - cacheKeyPrefix: cacheKeyPrefix, + appID: appID, + appSecret: appSecret, + cache: cache, + cacheKeyPrefix: cacheKeyPrefix, + accessTokenLock: new(sync.Mutex), } } @@ -130,7 +132,20 @@ func (ak *StableAccessToken) GetAccessTokenContext(ctx context.Context) (accessT // 先从cache中取 accessTokenCacheKey := fmt.Sprintf("%s_stable_access_token_%s", ak.cacheKeyPrefix, ak.appID) if val := ak.cache.Get(accessTokenCacheKey); val != nil { - return val.(string), nil + if accessToken = val.(string); accessToken != "" { + return + } + } + + // 加上lock,是为了防止在并发获取token时,cache刚好失效,导致从微信服务器上获取到不同token + ak.accessTokenLock.Lock() + defer ak.accessTokenLock.Unlock() + + // 双检,防止重复从微信服务器获取 + if val := ak.cache.Get(accessTokenCacheKey); val != nil { + if accessToken = val.(string); accessToken != "" { + return + } } // cache失效,从微信服务器获取 From 4a8371e1781574fc5672b468a0376fa0550f4a5e Mon Sep 17 00:00:00 2001 From: yangyl12345 Date: Wed, 27 Nov 2024 11:56:14 +0800 Subject: [PATCH 5/7] Feature/mini program template (#806) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 小程序发送订阅消息支持返回 msgid * 小程序发送订阅消息支持返回 msgid --------- Co-authored-by: w_yangyili --- miniprogram/subscribe/subscribe.go | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/miniprogram/subscribe/subscribe.go b/miniprogram/subscribe/subscribe.go index 9099044a..5be3bcfc 100644 --- a/miniprogram/subscribe/subscribe.go +++ b/miniprogram/subscribe/subscribe.go @@ -1,6 +1,7 @@ package subscribe import ( + "encoding/json" "fmt" "github.com/silenceper/wechat/v2/miniprogram/context" @@ -70,6 +71,13 @@ type TemplateList struct { Data []TemplateItem `json:"data"` } +// resTemplateSend 发送获取 msg id +type resTemplateSend struct { + util.CommonError + + MsgID int64 `json:"msgid"` +} + // Send 发送订阅消息 func (s *Subscribe) Send(msg *Message) (err error) { var accessToken string @@ -85,6 +93,33 @@ func (s *Subscribe) Send(msg *Message) (err error) { return util.DecodeWithCommonError(response, "Send") } +// SendGetMsgID 发送订阅消息返回 msgid +func (s *Subscribe) SendGetMsgID(msg *Message) (msgID int64, err error) { + var accessToken string + accessToken, err = s.GetAccessToken() + if err != nil { + return + } + uri := fmt.Sprintf("%s?access_token=%s", subscribeSendURL, accessToken) + response, err := util.PostJSON(uri, msg) + if err != nil { + return + } + + var result resTemplateSend + if err = json.Unmarshal(response, &result); 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 +} + // ListTemplates 获取当前帐号下的个人模板列表 // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.getTemplateList.html func (s *Subscribe) ListTemplates() (*TemplateList, error) { From 35af33f0bcb30b35d6df25486f05a513d801c4eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=B9=E6=99=B6?= Date: Fri, 20 Dec 2024 14:34:27 +0800 Subject: [PATCH 6/7] feat(media): add UploadImgFromReader api (#802) * feat(media): add getTempFile api add getTempFile api * feat(media): handle error in GetTempFile handle error in GetTempFile * feat(media): add UploadImgFromReader api add UploadImgFromReader api * fix(media): fixed not return common error fixed not return common error --- work/material/media.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/work/material/media.go b/work/material/media.go index c3c8e0c3..551eeee2 100644 --- a/work/material/media.go +++ b/work/material/media.go @@ -59,6 +59,30 @@ func (r *Client) UploadImg(filename string) (*UploadImgResponse, error) { return result, err } +// UploadImgFromReader 从 io.Reader 上传图片 +// @see https://developer.work.weixin.qq.com/document/path/90256 +func (r *Client) UploadImgFromReader(filename string, reader io.Reader) (*UploadImgResponse, error) { + var ( + accessToken string + err error + ) + if accessToken, err = r.GetAccessToken(); err != nil { + return nil, err + } + var byteData []byte + byteData, err = io.ReadAll(reader) + if err != nil { + return nil, err + } + var response []byte + if response, err = util.PostFileByStream("media", filename, fmt.Sprintf(uploadImgURL, accessToken), byteData); err != nil { + return nil, err + } + result := &UploadImgResponse{} + err = util.DecodeWithError(response, result, "UploadImg") + return result, err +} + // UploadTempFile 上传临时素材 // @see https://developer.work.weixin.qq.com/document/path/90253 // @mediaType 媒体文件类型,分别有图片(image)、语音(voice)、视频(video),普通文件(file) @@ -166,5 +190,13 @@ func (r *Client) GetTempFile(mediaID string) ([]byte, error) { if err != nil { return nil, err } + + // 检查响应是否为错误信息 + err = util.DecodeWithCommonError(response, "GetTempFile") + if err != nil { + return nil, err + } + + // 如果不是错误响应,则返回原始数据 return response, nil } From 3bd886d7f2136ec0c157ee1420a5b733bc8ddd0b Mon Sep 17 00:00:00 2001 From: markwang Date: Fri, 20 Dec 2024 14:34:48 +0800 Subject: [PATCH 7/7] =?UTF-8?q?feat:=20=E4=BC=81=E4=B8=9A=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1-=E9=80=9A=E8=AE=AF=E5=BD=95=E7=AE=A1=E7=90=86?= =?UTF-8?q?=EF=BC=8C=E6=96=B0=E5=A2=9E=E6=9B=B4=E6=96=B0=E6=88=90=E5=91=98?= =?UTF-8?q?=E3=80=81=E6=9B=B4=E6=96=B0=E9=83=A8=E9=97=A8=E3=80=81=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E9=83=A8=E9=97=A8=E6=96=B9=E6=B3=95=20(#799)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- work/addresslist/department.go | 49 +++++++++++++++++++++++++++++++++- work/addresslist/user.go | 47 ++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/work/addresslist/department.go b/work/addresslist/department.go index b2feca0b..5f65d660 100644 --- a/work/addresslist/department.go +++ b/work/addresslist/department.go @@ -9,12 +9,16 @@ import ( const ( // departmentCreateURL 创建部门 departmentCreateURL = "https://qyapi.weixin.qq.com/cgi-bin/department/create?access_token=%s" + // departmentUpdateURL 更新部门 + departmentUpdateURL = "https://qyapi.weixin.qq.com/cgi-bin/department/update?access_token=%s" + // departmentDeleteURL 删除部门 + departmentDeleteURL = "https://qyapi.weixin.qq.com/cgi-bin/department/delete?access_token=%s&id=%d" // departmentSimpleListURL 获取子部门ID列表 departmentSimpleListURL = "https://qyapi.weixin.qq.com/cgi-bin/department/simplelist?access_token=%s&id=%d" // departmentListURL 获取部门列表 departmentListURL = "https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token=%s" departmentListByIDURL = "https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token=%s&id=%d" - // departmentGetURL 获取单个部门详情 https://qyapi.weixin.qq.com/cgi-bin/department/get?access_token=ACCESS_TOKEN&id=ID + // departmentGetURL 获取单个部门详情 departmentGetURL = "https://qyapi.weixin.qq.com/cgi-bin/department/get?access_token=%s&id=%d" ) @@ -85,6 +89,49 @@ func (r *Client) DepartmentCreate(req *DepartmentCreateRequest) (*DepartmentCrea return result, err } +// DepartmentUpdateRequest 更新部门请求 +type DepartmentUpdateRequest struct { + ID int `json:"id"` + Name string `json:"name,omitempty"` + NameEn string `json:"name_en,omitempty"` + ParentID int `json:"parentid,omitempty"` + Order int `json:"order,omitempty"` +} + +// DepartmentUpdate 更新部门 +// see https://developer.work.weixin.qq.com/document/path/90206 +func (r *Client) DepartmentUpdate(req *DepartmentUpdateRequest) error { + var ( + accessToken string + err error + ) + if accessToken, err = r.GetAccessToken(); err != nil { + return err + } + var response []byte + if response, err = util.PostJSON(fmt.Sprintf(departmentUpdateURL, accessToken), req); err != nil { + return err + } + return util.DecodeWithCommonError(response, "DepartmentUpdate") +} + +// DepartmentDelete 删除部门 +// @see https://developer.work.weixin.qq.com/document/path/90207 +func (r *Client) DepartmentDelete(departmentID int) error { + var ( + accessToken string + err error + ) + if accessToken, err = r.GetAccessToken(); err != nil { + return err + } + var response []byte + if response, err = util.HTTPGet(fmt.Sprintf(departmentDeleteURL, accessToken, departmentID)); err != nil { + return err + } + return util.DecodeWithCommonError(response, "DepartmentDelete") +} + // DepartmentSimpleList 获取子部门ID列表 // see https://developer.work.weixin.qq.com/document/path/95350 func (r *Client) DepartmentSimpleList(departmentID int) ([]*DepartmentID, error) { diff --git a/work/addresslist/user.go b/work/addresslist/user.go index 61bb1461..b8092f7f 100644 --- a/work/addresslist/user.go +++ b/work/addresslist/user.go @@ -12,6 +12,8 @@ const ( userSimpleListURL = "https://qyapi.weixin.qq.com/cgi-bin/user/simplelist" // userCreateURL 创建成员 userCreateURL = "https://qyapi.weixin.qq.com/cgi-bin/user/create?access_token=%s" + // userUpdateURL 更新成员 + userUpdateURL = "https://qyapi.weixin.qq.com/cgi-bin/user/update?access_token=%s" // userGetURL 读取成员 userGetURL = "https://qyapi.weixin.qq.com/cgi-bin/user/get" // userDeleteURL 删除成员 @@ -154,6 +156,51 @@ func (r *Client) UserCreate(req *UserCreateRequest) (*UserCreateResponse, error) return result, err } +// UserUpdateRequest 更新成员请求 +type UserUpdateRequest struct { + UserID string `json:"userid"` + NewUserID string `json:"new_userid"` + Name string `json:"name"` + Alias string `json:"alias"` + Mobile string `json:"mobile"` + Department []int `json:"department"` + Order []int `json:"order"` + Position string `json:"position"` + Gender int `json:"gender"` + Email string `json:"email"` + BizMail string `json:"biz_mail"` + IsLeaderInDept []int `json:"is_leader_in_dept"` + DirectLeader []string `json:"direct_leader"` + Enable int `json:"enable"` + AvatarMediaid string `json:"avatar_mediaid"` + Telephone string `json:"telephone"` + Address string `json:"address"` + MainDepartment int `json:"main_department"` + Extattr struct { + Attrs []ExtraAttr `json:"attrs"` + } `json:"extattr"` + ToInvite bool `json:"to_invite"` + ExternalPosition string `json:"external_position"` + ExternalProfile ExternalProfile `json:"external_profile"` +} + +// UserUpdate 更新成员 +// see https://developer.work.weixin.qq.com/document/path/90197 +func (r *Client) UserUpdate(req *UserUpdateRequest) error { + var ( + accessToken string + err error + ) + if accessToken, err = r.GetAccessToken(); err != nil { + return err + } + var response []byte + if response, err = util.PostJSON(fmt.Sprintf(userUpdateURL, accessToken), req); err != nil { + return err + } + return util.DecodeWithCommonError(response, "UserUpdate") +} + // UserGetResponse 获取部门成员响应 type UserGetResponse struct { util.CommonError