From 86ceb6af2fdfdd2d115ca30358d354671b5cc1e3 Mon Sep 17 00:00:00 2001 From: "Mr. Yang" <625580134@qq.com> Date: Mon, 17 Oct 2022 16:52:19 +0800 Subject: [PATCH 01/27] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=B0=8F=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F=E5=AE=A1=E6=A0=B8=E7=BB=93=E6=9E=9C=E5=9B=9E=E8=B0=83?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=20(#630)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: yanglaosan --- officialaccount/message/message.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/officialaccount/message/message.go b/officialaccount/message/message.go index 2412cf9e5..8ab75a959 100644 --- a/officialaccount/message/message.go +++ b/officialaccount/message/message.go @@ -80,6 +80,12 @@ const ( EventSubscribeMsgPopupEvent EventType = "subscribe_msg_popup_event" // EventPublishJobFinish 发布任务完成 EventPublishJobFinish EventType = "PUBLISHJOBFINISH" + // EventWeappAuditSuccess 审核通过 + EventWeappAuditSuccess EventType = "weapp_audit_success" + // EventWeappAuditFail 审核不通过 + EventWeappAuditFail EventType = "weapp_audit_fail" + // EventWeappAuditDelay 审核延后 + EventWeappAuditDelay EventType = "weapp_audit_delay" ) const ( @@ -209,6 +215,13 @@ type MixMessage struct { // 设备相关 device.MsgDevice + + //小程序审核通知 + SuccTime int `xml:"SuccTime"` //审核成功时的时间戳 + FailTime int `xml:"FailTime"` //审核不通过的时间戳 + DelayTime int `xml:"DelayTime"` //审核延后时的时间戳 + Reason string `xml:"Reason"` //审核不通过的原因 + ScreenShot string `xml:"ScreenShot"` //审核不通过的截图示例。用 | 分隔的 media_id 的列表,可通过获取永久素材接口拉取截图内容 } // SubscribeMsgPopupEvent 订阅通知事件推送的消息体 From da3859261b780abd4a7201cfcab5eb5c74152b78 Mon Sep 17 00:00:00 2001 From: markwang <2951177317@qq.com> Date: Thu, 20 Oct 2022 14:34:51 +0800 Subject: [PATCH 02/27] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1-?= =?UTF-8?q?=E6=AC=A2=E8=BF=8E=E8=AF=AD=E7=B4=A0=E6=9D=90=E7=AE=A1=E7=90=86?= =?UTF-8?q?=EF=BC=8C=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1-=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E6=88=90=E5=91=98ID=E5=88=97=E8=A1=A8=20(#629)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 企业微信-客户联系-统计管理 * 企业微信-客户联系-统计管理 * 企业微信-客户联系-统计管理 * debug * rollback * debug * debug * 获取用户信息 * token * json.Marshal错误输出 * debug * bugfix * 企业微信-通讯录管理相关接口 * 企业微信-通讯录管理 * 企业微信-通讯录管理 * 企业微信-通讯录管理 * 企业微信-[联系我]方式新增和查询 * 企业微信-[联系我]方式新增和获取 * 企业微信-[联系我]方式更新 * 企业微信-[联系我]方式列表、删除 * json.Marshal错误输出 * 已实现接口bug修改 * 历史接口bugfix * 历史接口bugfix * comment * 企业微信:客户联系-消息推送;素材管理-上传图片 * fix * 企业微信-获取群发记录列表 * 历史接口bugfix * 1.企业微信-客户联系-消息推送-入群欢迎语素材管理 2.企业微信-通讯录管理-成员管理-获取成员ID列表 * golangci-lint * gofmt * 方法访问命名 Co-authored-by: wang.yu --- doc/api/work.md | 35 ++++---- work/addresslist/user.go | 42 ++++++++++ work/externalcontact/msg.go | 154 ++++++++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+), 15 deletions(-) diff --git a/doc/api/work.md b/doc/api/work.md index 9c4d78804..117c20b42 100644 --- a/doc/api/work.md +++ b/doc/api/work.md @@ -62,21 +62,25 @@ host: https://qyapi.weixin.qq.com/ ### 客户联系 [官方文档](https://developer.work.weixin.qq.com/document/path/92132/92133/92228) -| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 | 贡献者 | -|:------------------------:| -------- | :---------------------------------------| ---------- | ------------------------------- |----------| -| 获取「联系客户统计」数据 | POST | /cgi-bin/externalcontact/get_user_behavior_data | YES | (r *Client) GetUserBehaviorData | MARKWANG | -| 获取「群聊数据统计」数据 (按群主聚合的方式) | POST | /cgi-bin/externalcontact/groupchat/statistic | YES | (r *Client) GetGroupChatStat | MARKWANG | -| 获取「群聊数据统计」数据 (按自然日聚合的方式) | POST | /cgi-bin/externalcontact/groupchat/statistic_group_by_day | YES | (r *Client) GetGroupChatStatByDay | MARKWANG | -| 配置客户联系「联系我」方式 | POST | /cgi-bin/externalcontact/add_contact_way | YES | (r *Client) AddContactWay | MARKWANG | -| 获取企业已配置的「联系我」方式 | POST | /cgi-bin/externalcontact/get_contact_way | YES | (r *Client) GetContactWay | MARKWANG | -| 更新企业已配置的「联系我」方式 | POST | /cgi-bin/externalcontact/update_contact_way | YES | (r *Client) UpdateContactWay | MARKWANG | -| 获取企业已配置的「联系我」列表 | POST | /cgi-bin/externalcontact/list_contact_way | YES | (r *Client) ListContactWay | MARKWANG | -| 删除企业已配置的「联系我」方式 | POST | /cgi-bin/externalcontact/del_contact_way | YES | (r *Client) DelContactWay | MARKWANG | -| 创建企业群发 | POST | /cgi-bin/externalcontact/add_msg_template | YES | (r *Client) AddMsgTemplate | MARKWANG | -| 获取群发记录列表 | POST | /cgi-bin/externalcontact/get_groupmsg_list_v2 | YES | (r *Client) GetGroupMsgListV2 | MARKWANG | -| 获取群发成员发送任务列表 | POST | /cgi-bin/externalcontact/get_groupmsg_task | YES | (r *Client) GetGroupMsgTask | MARKWANG | -| 获取企业群发成员执行结果 | POST | /cgi-bin/externalcontact/get_groupmsg_send_result | YES | (r *Client) GetGroupMsgSendResult | MARKWANG | -| 发送新客户欢迎语 | POST | /cgi-bin/externalcontact/send_welcome_msg | YES | (r *Client) SendWelcomeMsg | MARKWANG | +| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 | 贡献者 | +|:------------------------:| -------- |:-------------------------------------------------------------| ---------- | ------------------------------- |----------| +| 获取「联系客户统计」数据 | POST | /cgi-bin/externalcontact/get_user_behavior_data | YES | (r *Client) GetUserBehaviorData | MARKWANG | +| 获取「群聊数据统计」数据 (按群主聚合的方式) | POST | /cgi-bin/externalcontact/groupchat/statistic | YES | (r *Client) GetGroupChatStat | MARKWANG | +| 获取「群聊数据统计」数据 (按自然日聚合的方式) | POST | /cgi-bin/externalcontact/groupchat/statistic_group_by_day | YES | (r *Client) GetGroupChatStatByDay | MARKWANG | +| 配置客户联系「联系我」方式 | POST | /cgi-bin/externalcontact/add_contact_way | YES | (r *Client) AddContactWay | MARKWANG | +| 获取企业已配置的「联系我」方式 | POST | /cgi-bin/externalcontact/get_contact_way | YES | (r *Client) GetContactWay | MARKWANG | +| 更新企业已配置的「联系我」方式 | POST | /cgi-bin/externalcontact/update_contact_way | YES | (r *Client) UpdateContactWay | MARKWANG | +| 获取企业已配置的「联系我」列表 | POST | /cgi-bin/externalcontact/list_contact_way | YES | (r *Client) ListContactWay | MARKWANG | +| 删除企业已配置的「联系我」方式 | POST | /cgi-bin/externalcontact/del_contact_way | YES | (r *Client) DelContactWay | MARKWANG | +| 创建企业群发 | POST | /cgi-bin/externalcontact/add_msg_template | YES | (r *Client) AddMsgTemplate | MARKWANG | +| 获取群发记录列表 | POST | /cgi-bin/externalcontact/get_groupmsg_list_v2 | YES | (r *Client) GetGroupMsgListV2 | MARKWANG | +| 获取群发成员发送任务列表 | POST | /cgi-bin/externalcontact/get_groupmsg_task | YES | (r *Client) GetGroupMsgTask | MARKWANG | +| 获取企业群发成员执行结果 | POST | /cgi-bin/externalcontact/get_groupmsg_send_result | YES | (r *Client) GetGroupMsgSendResult | MARKWANG | +| 发送新客户欢迎语 | POST | /cgi-bin/externalcontact/send_welcome_msg | YES | (r *Client) SendWelcomeMsg | MARKWANG | +| 添加入群欢迎语素材 | POST | /cgi-bin/externalcontact/group_welcome_template/add | YES | (r *Client) AddGroupWelcomeTemplate | MARKWANG | +| 编辑入群欢迎语素材 | POST | /cgi-bin/externalcontact/group_welcome_template/edit | YES | (r *Client) EditGroupWelcomeTemplate | MARKWANG | +| 获取入群欢迎语素材 | POST | /cgi-bin/externalcontact/group_welcome_template/get | YES | (r *Client) GetGroupWelcomeTemplate | MARKWANG | +| 删除入群欢迎语素材 | POST | /cgi-bin/externalcontact/group_welcome_template/del | YES | (r *Client) DelGroupWelcomeTemplate | MARKWANG | ## 通讯录管理 [官方文档](https://developer.work.weixin.qq.com/document/path/90193) @@ -87,6 +91,7 @@ host: https://qyapi.weixin.qq.com/ |:---------:|------|:----------------------------------------| ---------- | ------------------------------- |----------| | 获取子部门ID列表 | GET | /cgi-bin/department/simplelist | YES | (r *Client) DepartmentSimpleList| MARKWANG | | 获取部门成员 | GET | /cgi-bin/user/simplelist | YES | (r *Client) UserSimpleList | MARKWANG | +| 获取成员ID列表 | Post | /cgi-bin/user/list_id | YES | (r *Client) UserListId | MARKWANG | ## 素材管理 diff --git a/work/addresslist/user.go b/work/addresslist/user.go index f5f7c6b90..33e9fa185 100644 --- a/work/addresslist/user.go +++ b/work/addresslist/user.go @@ -11,6 +11,8 @@ const ( UserSimpleListURL = "https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?access_token=%s&department_id=%d" // UserGetURL 读取成员 UserGetURL = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s" + // UserListIDURL 获取成员ID列表 + UserListIDURL = "https://qyapi.weixin.qq.com/cgi-bin/user/list_id?access_token=%s" ) type ( @@ -133,3 +135,43 @@ func (r *Client) UserGet(UserID string) (*UserGetResponse, error) { } return result, nil } + +// UserListIDRequest 获取成员ID列表请求 +type UserListIDRequest struct { + Cursor string `json:"cursor"` + Limit int `json:"limit"` +} + +// UserListIDResponse 获取成员ID列表响应 +type UserListIDResponse struct { + util.CommonError + NextCursor string `json:"next_cursor"` + DeptUser []*DeptUser `json:"dept_user"` +} + +// DeptUser 用户-部门关系 +type DeptUser struct { + UserID string `json:"userid"` + Department int `json:"department"` +} + +// UserListID 获取成员ID列表 +// see https://developer.work.weixin.qq.com/document/path/96067 +func (r *Client) UserListID(req *UserListIDRequest) (*UserListIDResponse, error) { + var ( + accessToken string + err error + ) + if accessToken, err = r.GetAccessToken(); err != nil { + return nil, err + } + var response []byte + if response, err = util.PostJSON(fmt.Sprintf(UserListIDURL, accessToken), req); err != nil { + return nil, err + } + result := &UserListIDResponse{} + if err = util.DecodeWithError(response, result, "UserListID"); err != nil { + return nil, err + } + return result, nil +} diff --git a/work/externalcontact/msg.go b/work/externalcontact/msg.go index 3e8a7fb4d..4d72ab46e 100644 --- a/work/externalcontact/msg.go +++ b/work/externalcontact/msg.go @@ -17,6 +17,14 @@ const ( GetGroupMsgSendResultURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_groupmsg_send_result?access_token=%s" // SendWelcomeMsgURL 发送新客户欢迎语 SendWelcomeMsgURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/send_welcome_msg?access_token=%s" + // AddGroupWelcomeTemplateURL 添加入群欢迎语素材 + AddGroupWelcomeTemplateURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/add?access_token=%s" + // EditGroupWelcomeTemplateURL 编辑入群欢迎语素材 + EditGroupWelcomeTemplateURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/edit?access_token=%s" + // GetGroupWelcomeTemplateURL 获取入群欢迎语素材 + GetGroupWelcomeTemplateURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/get?access_token=%s" + // DelGroupWelcomeTemplateURL 删除入群欢迎语素材 + DelGroupWelcomeTemplateURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/del?access_token=%s" ) // AddMsgTemplateRequest 创建企业群发请求 @@ -268,3 +276,149 @@ func (r *Client) SendWelcomeMsg(req *SendWelcomeMsgRequest) error { } return nil } + +// AddGroupWelcomeTemplateRequest 添加入群欢迎语素材请求 +type AddGroupWelcomeTemplateRequest struct { + Text MsgText `json:"text"` + Image AttachmentImg `json:"image"` + Link AttachmentLink `json:"link"` + MiniProgram AttachmentMiniProgram `json:"miniprogram"` + File AttachmentFile `json:"file"` + Video AttachmentVideo `json:"video"` + AgentID int `json:"agentid"` + Notify int `json:"notify"` +} + +// AddGroupWelcomeTemplateResponse 添加入群欢迎语素材响应 +type AddGroupWelcomeTemplateResponse struct { + util.CommonError + TemplateID string `json:"template_id"` +} + +// AddGroupWelcomeTemplate 添加入群欢迎语素材 +// see https://developer.work.weixin.qq.com/document/path/92366#%E6%B7%BB%E5%8A%A0%E5%85%A5%E7%BE%A4%E6%AC%A2%E8%BF%8E%E8%AF%AD%E7%B4%A0%E6%9D%90 +func (r *Client) AddGroupWelcomeTemplate(req *AddGroupWelcomeTemplateRequest) (*AddGroupWelcomeTemplateResponse, error) { + var ( + accessToken string + err error + ) + if accessToken, err = r.GetAccessToken(); err != nil { + return nil, err + } + var response []byte + if response, err = util.PostJSON(fmt.Sprintf(AddGroupWelcomeTemplateURL, accessToken), req); err != nil { + return nil, err + } + result := &AddGroupWelcomeTemplateResponse{} + if err = util.DecodeWithError(response, result, "AddGroupWelcomeTemplate"); err != nil { + return nil, err + } + return result, nil +} + +// EditGroupWelcomeTemplateRequest 编辑入群欢迎语素材请求 +type EditGroupWelcomeTemplateRequest struct { + TemplateID string `json:"template_id"` + Text MsgText `json:"text"` + Image AttachmentImg `json:"image"` + Link AttachmentLink `json:"link"` + MiniProgram AttachmentMiniProgram `json:"miniprogram"` + File AttachmentFile `json:"file"` + Video AttachmentVideo `json:"video"` + AgentID int `json:"agentid"` +} + +// EditGroupWelcomeTemplateResponse 编辑入群欢迎语素材响应 +type EditGroupWelcomeTemplateResponse struct { + util.CommonError +} + +// EditGroupWelcomeTemplate 编辑入群欢迎语素材 +// see https://developer.work.weixin.qq.com/document/path/92366#%E7%BC%96%E8%BE%91%E5%85%A5%E7%BE%A4%E6%AC%A2%E8%BF%8E%E8%AF%AD%E7%B4%A0%E6%9D%90 +func (r *Client) EditGroupWelcomeTemplate(req *EditGroupWelcomeTemplateRequest) 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(EditGroupWelcomeTemplateURL, accessToken), req); err != nil { + return err + } + result := &EditGroupWelcomeTemplateResponse{} + if err = util.DecodeWithError(response, result, "EditGroupWelcomeTemplate"); err != nil { + return err + } + return nil +} + +// GetGroupWelcomeTemplateRequest 获取入群欢迎语素材请求 +type GetGroupWelcomeTemplateRequest struct { + TemplateID string `json:"template_id"` +} + +// GetGroupWelcomeTemplateResponse 获取入群欢迎语素材响应 +type GetGroupWelcomeTemplateResponse struct { + util.CommonError + Text MsgText `json:"text"` + Image AttachmentImg `json:"image"` + Link AttachmentLink `json:"link"` + MiniProgram AttachmentMiniProgram `json:"miniprogram"` + File AttachmentFile `json:"file"` + Video AttachmentVideo `json:"video"` +} + +// GetGroupWelcomeTemplate 获取入群欢迎语素材 +// see https://developer.work.weixin.qq.com/document/path/92366#%E8%8E%B7%E5%8F%96%E5%85%A5%E7%BE%A4%E6%AC%A2%E8%BF%8E%E8%AF%AD%E7%B4%A0%E6%9D%90 +func (r *Client) GetGroupWelcomeTemplate(req *GetGroupWelcomeTemplateRequest) (*GetGroupWelcomeTemplateResponse, error) { + var ( + accessToken string + err error + ) + if accessToken, err = r.GetAccessToken(); err != nil { + return nil, err + } + var response []byte + if response, err = util.PostJSON(fmt.Sprintf(GetGroupWelcomeTemplateURL, accessToken), req); err != nil { + return nil, err + } + result := &GetGroupWelcomeTemplateResponse{} + if err = util.DecodeWithError(response, result, "GetGroupWelcomeTemplate"); err != nil { + return nil, err + } + return result, nil +} + +// DelGroupWelcomeTemplateRequest 删除入群欢迎语素材请求 +type DelGroupWelcomeTemplateRequest struct { + TemplateID string `json:"template_id"` + AgentID int `json:"agentid"` +} + +// DelGroupWelcomeTemplateResponse 删除入群欢迎语素材响应 +type DelGroupWelcomeTemplateResponse struct { + util.CommonError +} + +// DelGroupWelcomeTemplate 删除入群欢迎语素材 +// see https://developer.work.weixin.qq.com/document/path/92366#%E5%88%A0%E9%99%A4%E5%85%A5%E7%BE%A4%E6%AC%A2%E8%BF%8E%E8%AF%AD%E7%B4%A0%E6%9D%90 +func (r *Client) DelGroupWelcomeTemplate(req *DelGroupWelcomeTemplateRequest) 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(DelGroupWelcomeTemplateURL, accessToken), req); err != nil { + return err + } + result := &DelGroupWelcomeTemplateResponse{} + if err = util.DecodeWithError(response, result, "DelGroupWelcomeTemplate"); err != nil { + return err + } + return nil +} From 3cb741ae1add73142ddb35f90ca45be05ede18c5 Mon Sep 17 00:00:00 2001 From: xuannanxan <382933169@qq.com> Date: Thu, 17 Nov 2022 09:28:42 +0800 Subject: [PATCH 03/27] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=BE=A4=E8=BF=9B=E7=BE=A4=E6=96=B9=E5=BC=8F?= =?UTF-8?q?=E9=85=8D=E7=BD=AE(#631)=20(#632)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- work/externalcontact/groupchat.go | 143 +++++++++++++++++++++++++++++ work/externalcontact/join_way.go | 146 ++++++++++++++++++++++++++++++ 2 files changed, 289 insertions(+) create mode 100644 work/externalcontact/groupchat.go create mode 100644 work/externalcontact/join_way.go diff --git a/work/externalcontact/groupchat.go b/work/externalcontact/groupchat.go new file mode 100644 index 000000000..0aa9ccd5b --- /dev/null +++ b/work/externalcontact/groupchat.go @@ -0,0 +1,143 @@ +package externalcontact + +import ( + "fmt" + + "github.com/silenceper/wechat/v2/util" +) + +// OpengIDToChatIDURL 客户群opengid转换URL +const OpengIDToChatIDURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/opengid_to_chatid" + +type ( + //GroupChatListRequest 获取客户群列表的请求参数 + GroupChatListRequest struct { + StatusFilter int `json:"status_filter"` // 非必填 客户群跟进状态过滤。0 - 所有列表(即不过滤) 1 - 离职待继承 2 - 离职继承中 3 - 离职继承完成 + OwnerFilter OwnerFilter `json:"owner_filter"` //非必填 群主过滤。如果不填,表示获取应用可见范围内全部群主的数据(但是不建议这么用,如果可见范围人数超过1000人,为了防止数据包过大,会报错 81017) + Cursor string `json:"cursor"` //非必填 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用不填 + Limit int `json:"limit"` //必填 分页,预期请求的数据量,取值范围 1 ~ 1000 + } + + //GroupChatList 客户群列表 + GroupChatList struct { + ChatID string `json:"chat_id"` + Status int `json:"status"` + } + //GroupChatListResponse 获取客户群列表的返回值 + GroupChatListResponse struct { + util.CommonError + GroupChatList []GroupChatList `json:"group_chat_list"` + NextCursor string `json:"next_cursor"` //游标 + } +) + +// GetGroupChatList 获取客户群列表 +// @see https://developer.work.weixin.qq.com/document/path/92120 +func (r *Client) GetGroupChatList(req *GroupChatListRequest) (*GroupChatListResponse, error) { + accessToken, err := r.GetAccessToken() + if err != nil { + return nil, err + } + var response []byte + response, err = util.PostJSON(fmt.Sprintf("%s/list?access_token=%s", GroupChatURL, accessToken), req) + if err != nil { + return nil, err + } + result := &GroupChatListResponse{} + if err = util.DecodeWithError(response, result, "GetGroupChatList"); err != nil { + return nil, err + } + return result, nil +} + +type ( + //GroupChatDetailRequest 客户群详情 请求参数 + GroupChatDetailRequest struct { + ChatID string `json:"chat_id"` + NeedName int `json:"need_name"` + } + //Invitor 邀请者 + Invitor struct { + UserID string `json:"userid"` //邀请者的userid + } + //GroupChatMember 群成员 + GroupChatMember struct { + UserID string `json:"userid"` //群成员id + Type int `json:"type"` //成员类型。 1 - 企业成员 2 - 外部联系人 + JoinTime int `json:"join_time"` //入群时间 + JoinScene int `json:"join_scene"` //入群方式 1 - 由群成员邀请入群(直接邀请入群) 2 - 由群成员邀请入群(通过邀请链接入群) 3 - 通过扫描群二维码入群 + Invitor Invitor `json:"invitor,omitempty"` //邀请者。目前仅当是由本企业内部成员邀请入群时会返回该值 + GroupNickname string `json:"group_nickname"` //在群里的昵称 + Name string `json:"name"` //名字。仅当 need_name = 1 时返回 如果是微信用户,则返回其在微信中设置的名字 如果是企业微信联系人,则返回其设置对外展示的别名或实名 + UnionID string `json:"unionid,omitempty"` //外部联系人在微信开放平台的唯一身份标识(微信unionid),通过此字段企业可将外部联系人与公众号/小程序用户关联起来。仅当群成员类型是微信用户(包括企业成员未添加好友),且企业绑定了微信开发者ID有此字段(查看绑定方法)。第三方不可获取,上游企业不可获取下游企业客户的unionid字段 + } + //GroupChatAdmin 群管理员 + GroupChatAdmin struct { + UserID string `json:"userid"` //群管理员userid + } + //GroupChat 客户群详情 + GroupChat struct { + ChatID string `json:"chat_id"` //客户群ID + Name string `json:"name"` //群名 + Owner string `json:"owner"` //群主ID + CreateTime int `json:"create_time"` //群的创建时间 + Notice string `json:"notice"` //群公告 + MemberList []GroupChatMember `json:"member_list"` //群成员列表 + AdminList []GroupChatAdmin `json:"admin_list"` //群管理员列表 + } + //GroupChatDetailResponse 客户群详情 返回值 + GroupChatDetailResponse struct { + util.CommonError + GroupChat GroupChat `json:"group_chat"` //客户群详情 + } +) + +// GetGroupChatDetail 获取客户群详情 +// @see https://developer.work.weixin.qq.com/document/path/92122 +func (r *Client) GetGroupChatDetail(req *GroupChatDetailRequest) (*GroupChatDetailResponse, error) { + accessToken, err := r.GetAccessToken() + if err != nil { + return nil, err + } + var response []byte + response, err = util.PostJSON(fmt.Sprintf("%s/get?access_token=%s", GroupChatURL, accessToken), req) + if err != nil { + return nil, err + } + result := &GroupChatDetailResponse{} + if err = util.DecodeWithError(response, result, "GetGroupChatDetail"); err != nil { + return nil, err + } + return result, nil +} + +type ( + //OpengIDToChatIDRequest 客户群opengid转换 请求参数 + OpengIDToChatIDRequest struct { + OpengID string `json:"opengid"` + } + //OpengIDToChatIDResponse 客户群opengid转换 返回值 + OpengIDToChatIDResponse struct { + util.CommonError + ChatID string `json:"chat_id"` //客户群ID + } +) + +// OpengIDToChatID 客户群opengid转换 +// @see https://developer.work.weixin.qq.com/document/path/94828 +func (r *Client) OpengIDToChatID(req *OpengIDToChatIDRequest) (*OpengIDToChatIDResponse, error) { + accessToken, err := r.GetAccessToken() + if err != nil { + return nil, err + } + var response []byte + response, err = util.PostJSON(fmt.Sprintf("%s?access_token=%s", OpengIDToChatIDURL, accessToken), req) + if err != nil { + return nil, err + } + result := &OpengIDToChatIDResponse{} + if err = util.DecodeWithError(response, result, "GetGroupChatDetail"); err != nil { + return nil, err + } + return result, nil +} diff --git a/work/externalcontact/join_way.go b/work/externalcontact/join_way.go new file mode 100644 index 000000000..239610369 --- /dev/null +++ b/work/externalcontact/join_way.go @@ -0,0 +1,146 @@ +package externalcontact + +import ( + "fmt" + + "github.com/silenceper/wechat/v2/util" +) + +// GroupChatURL 客户群 +const GroupChatURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat" + +type ( + // AddJoinWayRequest 添加群配置请求参数 + AddJoinWayRequest struct { + Scene int `json:"scene"` // 必填 1 - 群的小程序插件,2 - 群的二维码插件 + Remark string `json:"remark"` //非必填 联系方式的备注信息,用于助记,超过30个字符将被截断 + AutoCreateRoom int `json:"auto_create_room"` //非必填 当群满了后,是否自动新建群。0-否;1-是。 默认为1 + RoomBaseName string `json:"room_base_name"` //非必填 自动建群的群名前缀,当auto_create_room为1时有效。最长40个utf8字符 + RoomBaseID int `json:"room_base_id"` //非必填 自动建群的群起始序号,当auto_create_room为1时有效 + ChatIDList []string `json:"chat_id_list"` //必填 使用该配置的客户群ID列表,支持5个。见客户群ID获取方法 + State string `json:"state"` //非必填 企业自定义的state参数,用于区分不同的入群渠道。不超过30个UTF-8字符 + } + + // AddJoinWayResponse 添加群配置返回值 + AddJoinWayResponse struct { + util.CommonError + ConfigID string `json:"config_id"` + } +) + +// AddJoinWay 加入群聊 +// @see https://developer.work.weixin.qq.com/document/path/92229 +func (r *Client) AddJoinWay(req *AddJoinWayRequest) (*AddJoinWayResponse, error) { + var ( + accessToken string + err error + response []byte + ) + if accessToken, err = r.GetAccessToken(); err != nil { + return nil, err + } + response, err = util.PostJSON(fmt.Sprintf("%s/add_join_way?access_token=%s", GroupChatURL, accessToken), req) + if err != nil { + return nil, err + } + result := &AddJoinWayResponse{} + if err = util.DecodeWithError(response, result, "AddJoinWay"); err != nil { + return nil, err + } + return result, nil +} + +type ( + //JoinWayConfigRequest 获取或删除群配置的请求参数 + JoinWayConfigRequest struct { + ConfigID string `json:"config_id"` + } + + //JoinWay 群配置 + JoinWay struct { + ConfigID string `json:"config_id"` + Scene int `json:"scene"` + Remark string `json:"remark"` + AutoCreateRoom int `json:"auto_create_room"` + RoomBaseName string `json:"room_base_name"` + RoomBaseID int `json:"room_base_id"` + ChatIDList []string `json:"chat_id_list"` + QrCode string `json:"qr_code"` + State string `json:"state"` + } + //GetJoinWayResponse 获取群配置的返回值 + GetJoinWayResponse struct { + util.CommonError + JoinWay JoinWay `json:"join_way"` + } +) + +// GetJoinWay 获取客户群进群方式配置 +// @see https://developer.work.weixin.qq.com/document/path/92229 +func (r *Client) GetJoinWay(req *JoinWayConfigRequest) (*GetJoinWayResponse, error) { + var ( + accessToken string + err error + response []byte + ) + if accessToken, err = r.GetAccessToken(); err != nil { + return nil, err + } + response, err = util.PostJSON(fmt.Sprintf("%s/get_join_way?access_token=%s", GroupChatURL, accessToken), req) + if err != nil { + return nil, err + } + result := &GetJoinWayResponse{} + if err = util.DecodeWithError(response, result, "GetJoinWay"); err != nil { + return nil, err + } + return result, nil +} + +// UpdateJoinWayRequest 更新群配置的请求参数 +type UpdateJoinWayRequest struct { + ConfigID string `json:"config_id"` + Scene int `json:"scene"` // 必填 1 - 群的小程序插件,2 - 群的二维码插件 + Remark string `json:"remark"` //非必填 联系方式的备注信息,用于助记,超过30个字符将被截断 + AutoCreateRoom int `json:"auto_create_room"` //非必填 当群满了后,是否自动新建群。0-否;1-是。 默认为1 + RoomBaseName string `json:"room_base_name"` //非必填 自动建群的群名前缀,当auto_create_room为1时有效。最长40个utf8字符 + RoomBaseID int `json:"room_base_id"` //非必填 自动建群的群起始序号,当auto_create_room为1时有效 + ChatIDList []string `json:"chat_id_list"` //必填 使用该配置的客户群ID列表,支持5个。见客户群ID获取方法 + State string `json:"state"` //非必填 企业自定义的state参数,用于区分不同的入群渠道。不超过30个UTF-8字符 +} + +// UpdateJoinWay 更新客户群进群方式配置 +// @see https://developer.work.weixin.qq.com/document/path/92229 +func (r *Client) UpdateJoinWay(req *UpdateJoinWayRequest) error { + var ( + accessToken string + err error + response []byte + ) + if accessToken, err = r.GetAccessToken(); err != nil { + return err + } + response, err = util.PostJSON(fmt.Sprintf("%s/update_join_way?access_token=%s", GroupChatURL, accessToken), req) + if err != nil { + return err + } + return util.DecodeWithCommonError(response, "UpdateJoinWay") +} + +// DelJoinWay 删除客户群进群方式配置 +// @see https://developer.work.weixin.qq.com/document/path/92229 +func (r *Client) DelJoinWay(req *JoinWayConfigRequest) error { + var ( + accessToken string + err error + response []byte + ) + if accessToken, err = r.GetAccessToken(); err != nil { + return err + } + response, err = util.PostJSON(fmt.Sprintf("%s/del_join_way?access_token=%s", GroupChatURL, accessToken), req) + if err != nil { + return err + } + return util.DecodeWithCommonError(response, "DelJoinWay") +} From abd583df0166d110610d5e2f5984cb61c80ca25d Mon Sep 17 00:00:00 2001 From: silenceper Date: Sun, 1 Jan 2023 23:54:58 +0800 Subject: [PATCH 04/27] =?UTF-8?q?fix=20#636=20=E7=94=9F=E6=88=90=E7=9F=AD?= =?UTF-8?q?=E9=93=BE=E6=96=B9=E6=B3=95=E6=8A=A5commonError=20is=20invalid?= =?UTF-8?q?=20or=20not=20struct=20(#637)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- miniprogram/shortlink/shortlink.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miniprogram/shortlink/shortlink.go b/miniprogram/shortlink/shortlink.go index b681e336f..34c1700c4 100644 --- a/miniprogram/shortlink/shortlink.go +++ b/miniprogram/shortlink/shortlink.go @@ -37,7 +37,7 @@ type ShortLinker struct { // resShortLinker 返回结构体 type resShortLinker struct { // 通用错误 - *util.CommonError + util.CommonError // 返回的 shortLink Link string `json:"link"` From 04559ed4bbaa93ead4cca37c555a6f92d3557c73 Mon Sep 17 00:00:00 2001 From: Lien Li Date: Tue, 3 Jan 2023 10:07:45 +0800 Subject: [PATCH 05/27] =?UTF-8?q?fix=EF=BC=9A=E9=81=BF=E5=85=8D=E6=B8=B8?= =?UTF-8?q?=E6=A0=87=E6=8B=BC=E6=8E=A5=E5=BC=82=E5=B8=B8=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E5=8F=96=E4=B8=8D=E5=88=B0=E6=95=B0=E6=8D=AE=20(#638)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- work/externalcontact/external_user.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/work/externalcontact/external_user.go b/work/externalcontact/external_user.go index 434f1bbb3..acbc01343 100644 --- a/work/externalcontact/external_user.go +++ b/work/externalcontact/external_user.go @@ -103,7 +103,11 @@ func (r *Client) GetExternalUserDetail(externalUserID string, nextCursor ...stri return nil, err } var response []byte - response, err = util.HTTPGet(fmt.Sprintf("%s?access_token=%v&external_userid=%v&cursor=%v", FetchExternalContactUserDetailURL, accessToken, externalUserID, nextCursor)) + var cursor string + if len(nextCursor) > 0 { + cursor = nextCursor[0] + } + response, err = util.HTTPGet(fmt.Sprintf("%s?access_token=%v&external_userid=%v&cursor=%v", FetchExternalContactUserDetailURL, accessToken, externalUserID, cursor)) if err != nil { return nil, err } From 1a9dbc493bb14da472269834e5f9ddf914ee0557 Mon Sep 17 00:00:00 2001 From: Lien Li Date: Wed, 4 Jan 2023 10:26:16 +0800 Subject: [PATCH 06/27] =?UTF-8?q?feat=EF=BC=9A=E6=94=AF=E6=8C=81=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E8=81=94=E7=B3=BB=E7=9A=84=E6=9C=8D=E5=8A=A1=E7=AB=AF?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=20(#639)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://developer.work.weixin.qq.com/document/path/92130 --- work/externalcontact/callback.go | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 work/externalcontact/callback.go diff --git a/work/externalcontact/callback.go b/work/externalcontact/callback.go new file mode 100644 index 000000000..f57034038 --- /dev/null +++ b/work/externalcontact/callback.go @@ -0,0 +1,45 @@ +package externalcontact + +import ( + "encoding/xml" + + "github.com/silenceper/wechat/v2/util" +) + +// 原始回调消息内容 +type callbackOriginMessage struct { + ToUserName string // 企业微信的CorpID,当为第三方套件回调事件时,CorpID的内容为suiteid + AgentID string // 接收的应用id,可在应用的设置页面获取 + Encrypt string // 消息结构体加密后的字符串 +} + +// EventCallbackMessage 微信客户联系回调消息 +// https://developer.work.weixin.qq.com/document/path/92130 +type EventCallbackMessage struct { + ToUserName string `json:"to_user_name"` + FromUserName string `json:"from_user_name"` + CreateTime int64 `json:"create_time"` + MsgType string `json:"msg_type"` + Event string `json:"event"` + ChangeType string `json:"change_type"` + UserID string `json:"user_id"` + ExternalUserID string `json:"external_user_id"` + State string `json:"state"` + WelcomeCode string `json:"welcome_code"` +} + +// GetCallbackMessage 获取联系客户回调事件中的消息内容 +func (r *Client) GetCallbackMessage(encryptedMsg []byte) (msg EventCallbackMessage, err error) { + var origin callbackOriginMessage + if err = xml.Unmarshal(encryptedMsg, &origin); err != nil { + return + } + _, bData, err := util.DecryptMsg(r.CorpID, origin.Encrypt, r.EncodingAESKey) + if err != nil { + return + } + if err = xml.Unmarshal(bData, &msg); err != nil { + return + } + return +} From d1d034eb9520c569abfe56110afe567f4f7d94cd Mon Sep 17 00:00:00 2001 From: daguang <28806852+DGuang21@users.noreply.github.com> Date: Sun, 29 Jan 2023 11:58:00 +0800 Subject: [PATCH 07/27] oauth add snapshot user mark field (#643) --- officialaccount/oauth/oauth.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/officialaccount/oauth/oauth.go b/officialaccount/oauth/oauth.go index 385fe9b74..c7c647a3d 100644 --- a/officialaccount/oauth/oauth.go +++ b/officialaccount/oauth/oauth.go @@ -64,6 +64,10 @@ type ResAccessToken struct { OpenID string `json:"openid"` Scope string `json:"scope"` + // IsSnapShotUser 是否为快照页模式虚拟账号,只有当用户是快照页模式虚拟账号时返回,值为1 + // 公众号文档 https://developers.weixin.qq.com/community/minihome/doc/000c2c34068880629ced91a2f56001 + IsSnapShotUser int `json:"is_snapshotuser"` + // UnionID 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。 // 公众号文档 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842 UnionID string `json:"unionid"` From ab354c4d03dee90a619eaac21836a6b6c48b5511 Mon Sep 17 00:00:00 2001 From: Lien Li Date: Mon, 6 Feb 2023 08:43:41 +0800 Subject: [PATCH 08/27] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E4=BC=81?= =?UTF-8?q?=E4=B8=9A=E5=BE=AE=E4=BF=A1=E4=B8=8A=E4=BC=A0=E4=B8=B4=E6=97=B6?= =?UTF-8?q?=E7=B4=A0=E6=9D=90=20(#644)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- work/material/media.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/work/material/media.go b/work/material/media.go index 0d2ac8d67..cb37344c7 100644 --- a/work/material/media.go +++ b/work/material/media.go @@ -9,6 +9,9 @@ import ( const ( // UploadImgURL 上传图片 UploadImgURL = "https://qyapi.weixin.qq.com/cgi-bin/media/uploadimg?access_token=%s" + + // UploadTempFile 上传临时素材 + UploadTempFile = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s" ) // UploadImgResponse 上传图片响应 @@ -17,6 +20,14 @@ type UploadImgResponse struct { URL string `json:"url"` } +// UploadTempFileResponse 上传临时素材响应 +type UploadTempFileResponse struct { + util.CommonError + MediaID string `json:"media_id"` + CreateAt string `json:"created_at"` + Type string `json:"type"` +} + // UploadImg 上传图片 // @see https://developer.work.weixin.qq.com/document/path/90256 func (r *Client) UploadImg(filename string) (*UploadImgResponse, error) { @@ -37,3 +48,25 @@ func (r *Client) UploadImg(filename string) (*UploadImgResponse, error) { } return result, nil } + +// UploadTempFile 上传临时素材 +// @see https://developer.work.weixin.qq.com/document/path/90253 +// @mediaType 媒体文件类型,分别有图片(image)、语音(voice)、视频(video),普通文件(file) +func (r *Client) UploadTempFile(filename string, mediaType string) (*UploadTempFileResponse, error) { + var ( + accessToken string + err error + ) + if accessToken, err = r.GetAccessToken(); err != nil { + return nil, err + } + var response []byte + if response, err = util.PostFile("media", filename, fmt.Sprintf(UploadTempFile, accessToken, mediaType)); err != nil { + return nil, err + } + result := &UploadTempFileResponse{} + if err = util.DecodeWithError(response, result, "UploadTempFile"); err != nil { + return nil, err + } + return result, nil +} From 6e80b71cb2035b4f8e470e343f7984a8db4e8b16 Mon Sep 17 00:00:00 2001 From: johnpoint Date: Wed, 8 Feb 2023 21:19:37 +0800 Subject: [PATCH 09/27] =?UTF-8?q?=E9=81=BF=E5=85=8D=E6=97=A0=E6=95=88?= =?UTF-8?q?=E7=9A=84access=5Ftoken=E7=95=99=E5=9C=A8cache=20(#645)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openplatform/context/accessToken.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openplatform/context/accessToken.go b/openplatform/context/accessToken.go index 7d7f47d5d..a8316e2ef 100644 --- a/openplatform/context/accessToken.go +++ b/openplatform/context/accessToken.go @@ -193,7 +193,7 @@ func (ctx *Context) RefreshAuthrToken(appid, refreshToken string) (*AuthrAccessT } authrTokenKey := "authorizer_access_token_" + appid - if err := ctx.Cache.Set(authrTokenKey, ret.AccessToken, time.Minute*80); err != nil { + if err := ctx.Cache.Set(authrTokenKey, ret.AccessToken, time.Second*time.Duration(ret.ExpiresIn-30)); err != nil { return nil, err } return ret, nil From 429219b53f02d5f07e5326c4b530595623e3aea6 Mon Sep 17 00:00:00 2001 From: just5325 <532508307@qq.com> Date: Fri, 10 Feb 2023 17:11:02 +0800 Subject: [PATCH 10/27] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E6=8E=A8=E9=80=81=E6=A8=A1=E5=9D=97=EF=BC=8C=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E9=83=A8=E5=88=86=E5=B8=B8=E7=94=A8=E6=B6=88=E6=81=AF=E6=8E=A8?= =?UTF-8?q?=E9=80=81=EF=BC=9ASendText=20=E5=8F=91=E9=80=81=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E6=B6=88=E6=81=AF=E3=80=81SendImage=20=E5=8F=91?= =?UTF-8?q?=E9=80=81=E5=9B=BE=E7=89=87=E6=B6=88=E6=81=AF=E3=80=81SendVoice?= =?UTF-8?q?=20=E5=8F=91=E9=80=81=E8=AF=AD=E9=9F=B3=E6=B6=88=E6=81=AF=20(#6?= =?UTF-8?q?49)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 企业微信消息推送接口:https://qyapi.weixin.qq.com/cgi-bin/message/send --- work/message/client.go | 16 +++++ work/message/message.go | 132 ++++++++++++++++++++++++++++++++++++++++ work/work.go | 6 ++ 3 files changed, 154 insertions(+) create mode 100644 work/message/client.go create mode 100644 work/message/message.go diff --git a/work/message/client.go b/work/message/client.go new file mode 100644 index 000000000..b50b37e77 --- /dev/null +++ b/work/message/client.go @@ -0,0 +1,16 @@ +// Package message 消息推送,实现企业微信消息推送相关接口:https://developer.work.weixin.qq.com/document/path/90235 +package message + +import ( + "github.com/silenceper/wechat/v2/work/context" +) + +// Client 消息推送接口实例 +type Client struct { + *context.Context +} + +// NewClient 初始化实例 +func NewClient(ctx *context.Context) *Client { + return &Client{ctx} +} diff --git a/work/message/message.go b/work/message/message.go new file mode 100644 index 000000000..2598688be --- /dev/null +++ b/work/message/message.go @@ -0,0 +1,132 @@ +package message + +import ( + "encoding/json" + "fmt" + + "github.com/silenceper/wechat/v2/util" +) + +const ( + // 发送应用消息的接口地址 + sendURL = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s" +) + +type ( + // SendRequestCommon 发送应用消息请求公共参数 + SendRequestCommon struct { + // 指定接收消息的成员,成员ID列表(多个接收者用‘|’分隔,最多支持1000个)。 特殊情况:指定为"@all",则向该企业应用的全部成员发送 + ToUser string `json:"touser"` + // 指定接收消息的部门,部门ID列表,多个接收者用‘|’分隔,最多支持100个。 当touser为"@all"时忽略本参数 + ToParty string `json:"toparty"` + // 指定接收消息的标签,标签ID列表,多个接收者用‘|’分隔,最多支持100个。 当touser为"@all"时忽略本参数 + ToTag string `json:"totag"` + // 消息类型,此时固定为:text + MsgType string `json:"msgtype"` + // 企业应用的id,整型。企业内部开发,可在应用的设置页面查看;第三方服务商,可通过接口 获取企业授权信息 获取该参数值 + AgentID string `json:"agentid"` + // 表示是否是保密消息,0表示可对外分享,1表示不能分享且内容显示水印,默认为0 + Safe int `json:"safe"` + // 表示是否开启id转译,0表示否,1表示是,默认0。仅第三方应用需要用到,企业自建应用可以忽略。 + EnableIDTrans int `json:"enable_id_trans"` + // 表示是否开启重复消息检查,0表示否,1表示是,默认0 + EnableDuplicateCheck int `json:"enable_duplicate_check"` + // 表示是否重复消息检查的时间间隔,默认1800s,最大不超过4小时 + DuplicateCheckInterval int `json:"duplicate_check_interval"` + } + // SendResponse 发送应用消息响应参数 + SendResponse struct { + util.CommonError + InvalidUser string `json:"invaliduser"` // 不合法的userid,不区分大小写,统一转为小写 + InvalidParty string `json:"invalidparty"` // 不合法的partyid + InvalidTag string `json:"invalidtag"` // 不合法的标签id + UnlicensedUser string `json:"unlicenseduser"` // 没有基础接口许可(包含已过期)的userid + MsgID string `json:"msgid"` // 消息id + ResponseCode string `json:"response_code"` + } + + // SendTextRequest 发送文本消息的请求 + SendTextRequest struct { + *SendRequestCommon + Text TextField `json:"text"` + } + // TextField 文本消息参数 + TextField struct { + // 消息内容,最长不超过2048个字节,超过将截断(支持id转译) + Content string `json:"content"` + } + + // SendImageRequest 发送图片消息的请求 + SendImageRequest struct { + *SendRequestCommon + Image ImageField `json:"image"` + } + // ImageField 图片消息参数 + ImageField struct { + // 图片媒体文件id,可以调用上传临时素材接口获取 + MediaID string `json:"media_id"` + } + + // SendVoiceRequest 发送语音消息的请求 + SendVoiceRequest struct { + *SendRequestCommon + Voice VoiceField `json:"voice"` + } + // VoiceField 语音消息参数 + VoiceField struct { + // 语音文件id,可以调用上传临时素材接口获取 + MediaID string `json:"media_id"` + } +) + +// Send 发送应用消息 +// @desc 实现企业微信发送应用消息接口:https://developer.work.weixin.qq.com/document/path/90236 +func (r *Client) Send(apiName string, request interface{}) (*SendResponse, error) { + // 获取accessToken + accessToken, err := r.GetAccessToken() + if err != nil { + return nil, err + } + // 请求参数转 JSON 格式 + jsonData, err := json.Marshal(request) + if err != nil { + return nil, err + } + // 发起http请求 + response, err := util.HTTPPost(fmt.Sprintf(sendURL, accessToken), string(jsonData)) + if err != nil { + return nil, err + } + // 按照结构体解析返回值 + result := &SendResponse{} + if err = util.DecodeWithError(response, result, apiName); err != nil { + return nil, err + } + // 返回数据 + return result, nil +} + +// SendText 发送文本消息 +func (r *Client) SendText(request SendTextRequest) (*SendResponse, error) { + // 发送文本消息MsgType参数固定为:text + request.MsgType = "text" + return r.Send("MessageSendText", request) +} + +// SendImage 发送图片消息 +func (r *Client) SendImage(request SendImageRequest) (*SendResponse, error) { + // 发送图片消息MsgType参数固定为:image + request.MsgType = "image" + return r.Send("MessageSendImage", request) +} + +// SendVoice 发送语音消息 +func (r *Client) SendVoice(request SendVoiceRequest) (*SendResponse, error) { + // 发送语音消息MsgType参数固定为:voice + request.MsgType = "voice" + return r.Send("MessageSendVoice", request) +} + +// 以上实现了部分常用消息推送:SendText 发送文本消息、SendImage 发送图片消息、SendVoice 发送语音消息, +// 如需扩展其他消息类型,建议按照以上格式,扩展对应消息类型的参数即可 +// 也可以直接使用Send方法,按照企业微信消息推送的接口文档传对应消息类型的参数来使用 diff --git a/work/work.go b/work/work.go index 6e4e98e1f..869809bd3 100644 --- a/work/work.go +++ b/work/work.go @@ -8,6 +8,7 @@ import ( "github.com/silenceper/wechat/v2/work/externalcontact" "github.com/silenceper/wechat/v2/work/kf" "github.com/silenceper/wechat/v2/work/material" + "github.com/silenceper/wechat/v2/work/message" "github.com/silenceper/wechat/v2/work/msgaudit" "github.com/silenceper/wechat/v2/work/oauth" "github.com/silenceper/wechat/v2/work/robot" @@ -67,3 +68,8 @@ func (wk *Work) GetMaterial() *material.Client { func (wk *Work) GetRobot() *robot.Client { return robot.NewClient(wk.ctx) } + +// GetMessage get robot +func (wk *Work) GetMessage() *message.Client { + return message.NewClient(wk.ctx) +} From d39615f2fa692a3610cd732b709076ec64885c0b Mon Sep 17 00:00:00 2001 From: realpg Date: Sat, 11 Feb 2023 23:50:33 +0800 Subject: [PATCH 11/27] =?UTF-8?q?=E8=A1=A5=E5=85=A8=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E6=B6=88=E6=81=AFAPI=E4=B8=AD=E9=81=97=E6=BC=8F=E7=9A=84=20?= =?UTF-8?q?=E2=80=98=E9=98=B2=E9=87=8D=E5=85=A5ID=E2=80=99=20=E5=8F=82?= =?UTF-8?q?=E6=95=B0=20(#651)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit client_msg_id: 防重入id。 对于同一个openid + client_msg_id, 只发送一条消息,10分钟有效,超过10分钟不保证效果。若无防重入需求,可不填 --- officialaccount/message/template.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/officialaccount/message/template.go b/officialaccount/message/template.go index 79e2e05b1..24a47ba26 100644 --- a/officialaccount/message/template.go +++ b/officialaccount/message/template.go @@ -29,11 +29,12 @@ func NewTemplate(context *context.Context) *Template { // TemplateMessage 发送的模板消息内容 type TemplateMessage struct { - ToUser string `json:"touser"` // 必须, 接受者OpenID - TemplateID string `json:"template_id"` // 必须, 模版ID - URL string `json:"url,omitempty"` // 可选, 用户点击后跳转的URL, 该URL必须处于开发者在公众平台网站中设置的域中 - Color string `json:"color,omitempty"` // 可选, 整个消息的颜色, 可以不设置 - Data map[string]*TemplateDataItem `json:"data"` // 必须, 模板数据 + ToUser string `json:"touser"` // 必须, 接受者OpenID + TemplateID string `json:"template_id"` // 必须, 模版ID + URL string `json:"url,omitempty"` // 可选, 用户点击后跳转的URL, 该URL必须处于开发者在公众平台网站中设置的域中 + Color string `json:"color,omitempty"` // 可选, 整个消息的颜色, 可以不设置 + Data map[string]*TemplateDataItem `json:"data"` // 必须, 模板数据 + ClientMsgID string `json:"client_msg_id,omitempty"` // 可选, 防重入ID MiniProgram struct { AppID string `json:"appid"` // 所需跳转到的小程序appid(该小程序appid必须与发模板消息的公众号是绑定关联关系) From 9c0189340b636f860f2955ebe0c0b2eef1c1f1f2 Mon Sep 17 00:00:00 2001 From: johnpoint Date: Tue, 14 Feb 2023 12:04:44 +0800 Subject: [PATCH 12/27] =?UTF-8?q?pref:=20=E5=8F=98=E9=87=8F=E5=91=BD?= =?UTF-8?q?=E5=90=8D=20&=20url=20(#650)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- officialaccount/ocr/ocr.go | 56 ++++++++++++++------------------------ 1 file changed, 21 insertions(+), 35 deletions(-) diff --git a/officialaccount/ocr/ocr.go b/officialaccount/ocr/ocr.go index 471d87f5a..2420a804e 100644 --- a/officialaccount/ocr/ocr.go +++ b/officialaccount/ocr/ocr.go @@ -154,134 +154,120 @@ func NewOCR(c *context.Context) *OCR { } // IDCard 身份证OCR识别接口 -func (ocr *OCR) IDCard(path string) (ResIDCard ResIDCard, err error) { +func (ocr *OCR) IDCard(path string) (resIDCard ResIDCard, err error) { accessToken, err := ocr.GetAccessToken() if err != nil { return } - uri := fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrIDCardURL, url.QueryEscape(path), accessToken) - - response, err := util.HTTPPost(uri, "") + response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrIDCardURL, url.QueryEscape(path), accessToken), "") if err != nil { return } - err = util.DecodeWithError(response, &ResIDCard, "OCRIDCard") + err = util.DecodeWithError(response, &resIDCard, "OCRIDCard") return } // BankCard 银行卡OCR识别接口 -func (ocr *OCR) BankCard(path string) (ResBankCard ResBankCard, err error) { +func (ocr *OCR) BankCard(path string) (resBankCard ResBankCard, err error) { accessToken, err := ocr.GetAccessToken() if err != nil { return } - uri := fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrBankCardURL, url.QueryEscape(path), accessToken) - - response, err := util.HTTPPost(uri, "") + response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrBankCardURL, url.QueryEscape(path), accessToken), "") if err != nil { return } - err = util.DecodeWithError(response, &ResBankCard, "OCRBankCard") + err = util.DecodeWithError(response, &resBankCard, "OCRBankCard") return } // Driving 行驶证OCR识别接口 -func (ocr *OCR) Driving(path string) (ResDriving ResDriving, err error) { +func (ocr *OCR) Driving(path string) (resDriving ResDriving, err error) { accessToken, err := ocr.GetAccessToken() if err != nil { return } - uri := fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrDrivingURL, url.QueryEscape(path), accessToken) - - response, err := util.HTTPPost(uri, "") + response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrDrivingURL, url.QueryEscape(path), accessToken), "") if err != nil { return } - err = util.DecodeWithError(response, &ResDriving, "OCRDriving") + err = util.DecodeWithError(response, &resDriving, "OCRDriving") return } // DrivingLicense 驾驶证OCR识别接口 -func (ocr *OCR) DrivingLicense(path string) (ResDrivingLicense ResDrivingLicense, err error) { +func (ocr *OCR) DrivingLicense(path string) (resDrivingLicense ResDrivingLicense, err error) { accessToken, err := ocr.GetAccessToken() if err != nil { return } - uri := fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrDrivingLicenseURL, url.QueryEscape(path), accessToken) - - response, err := util.HTTPPost(uri, "") + response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrDrivingLicenseURL, url.QueryEscape(path), accessToken), "") if err != nil { return } - err = util.DecodeWithError(response, &ResDrivingLicense, "OCRDrivingLicense") + err = util.DecodeWithError(response, &resDrivingLicense, "OCRDrivingLicense") return } // BizLicense 营业执照OCR识别接口 -func (ocr *OCR) BizLicense(path string) (ResBizLicense ResBizLicense, err error) { +func (ocr *OCR) BizLicense(path string) (resBizLicense ResBizLicense, err error) { accessToken, err := ocr.GetAccessToken() if err != nil { return } - uri := fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrBizLicenseURL, url.QueryEscape(path), accessToken) - - response, err := util.HTTPPost(uri, "") + response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrBizLicenseURL, url.QueryEscape(path), accessToken), "") if err != nil { return } - err = util.DecodeWithError(response, &ResBizLicense, "OCRBizLicense") + err = util.DecodeWithError(response, &resBizLicense, "OCRBizLicense") return } // Common 通用印刷体OCR识别接口 -func (ocr *OCR) Common(path string) (ResCommon ResCommon, err error) { +func (ocr *OCR) Common(path string) (resCommon ResCommon, err error) { accessToken, err := ocr.GetAccessToken() if err != nil { return } - uri := fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrCommonURL, url.QueryEscape(path), accessToken) - - response, err := util.HTTPPost(uri, "") + response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrCommonURL, url.QueryEscape(path), accessToken), "") if err != nil { return } - err = util.DecodeWithError(response, &ResCommon, "OCRCommon") + err = util.DecodeWithError(response, &resCommon, "OCRCommon") return } // PlateNumber 车牌OCR识别接口 -func (ocr *OCR) PlateNumber(path string) (ResPlateNumber ResPlateNumber, err error) { +func (ocr *OCR) PlateNumber(path string) (resPlateNumber ResPlateNumber, err error) { accessToken, err := ocr.GetAccessToken() if err != nil { return } - uri := fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrPlateNumberURL, url.QueryEscape(path), accessToken) - - response, err := util.HTTPPost(uri, "") + response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrPlateNumberURL, url.QueryEscape(path), accessToken), "") if err != nil { return } - err = util.DecodeWithError(response, &ResPlateNumber, "OCRPlateNumber") + err = util.DecodeWithError(response, &resPlateNumber, "OCRPlateNumber") return } From 494082ff4f6f5440dda1267a953b4d030ad1b0af Mon Sep 17 00:00:00 2001 From: markwang <2951177317@qq.com> Date: Tue, 14 Mar 2023 19:18:20 +0800 Subject: [PATCH 13/27] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1-?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E8=8E=B7=E5=8F=96=E5=AE=A2=E6=88=B7=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E5=85=A5=E5=8F=82=E4=BC=98=E5=8C=96=20(#657)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 企业微信-批量获取客户详情入参优化 --------- Co-authored-by: wang.yu Co-authored-by: markwang --- work/externalcontact/external_user.go | 1 + 1 file changed, 1 insertion(+) diff --git a/work/externalcontact/external_user.go b/work/externalcontact/external_user.go index acbc01343..b8d8f9c63 100644 --- a/work/externalcontact/external_user.go +++ b/work/externalcontact/external_user.go @@ -123,6 +123,7 @@ func (r *Client) GetExternalUserDetail(externalUserID string, nextCursor ...stri type BatchGetExternalUserDetailsRequest struct { UserIDList []string `json:"userid_list"` Cursor string `json:"cursor"` + Limit int `json:"limit,omitempty"` } // ExternalUserDetailListResponse 批量获取外部联系人详情响应 From fbda048f620744d6de40917b4a7742136482d574 Mon Sep 17 00:00:00 2001 From: markwang <2951177317@qq.com> Date: Tue, 21 Mar 2023 23:58:37 +0800 Subject: [PATCH 14/27] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1-?= =?UTF-8?q?=E9=80=9A=E8=AE=AF=E5=BD=95=E7=AE=A1=E7=90=86-=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=E7=AE=A1=E7=90=86-=E5=88=9B=E5=BB=BA/=E6=9B=B4?= =?UTF-8?q?=E6=96=B0/=E5=88=A0=E9=99=A4=E6=A0=87=E7=AD=BE=20(#659)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- work/addresslist/tag.go | 92 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 work/addresslist/tag.go diff --git a/work/addresslist/tag.go b/work/addresslist/tag.go new file mode 100644 index 000000000..fe12acdf4 --- /dev/null +++ b/work/addresslist/tag.go @@ -0,0 +1,92 @@ +package addresslist + +import ( + "fmt" + + "github.com/silenceper/wechat/v2/util" +) + +const ( + // createTagURL 创建标签 + createTagURL = "https://qyapi.weixin.qq.com/cgi-bin/tag/create?access_token=%s" + // updateTagURL 更新标签名字 + updateTagURL = "https://qyapi.weixin.qq.com/cgi-bin/tag/update?access_token=%s" + // deleteTagURL 删除标签 + deleteTagURL = "https://qyapi.weixin.qq.com/cgi-bin/tag/delete?access_token=%s&tagid=%d" +) + +type ( + // CreateTagRequest 创建标签请求 + CreateTagRequest struct { + TagName string `json:"tagname"` + TagID int `json:"tagid,omitempty"` + } + // CreateTagResponse 创建标签响应 + CreateTagResponse struct { + util.CommonError + TagID int `json:"tagid"` + } +) + +// CreateTag 创建标签 +// see https://developer.work.weixin.qq.com/document/path/90210 +func (r *Client) CreateTag(req *CreateTagRequest) (*CreateTagResponse, error) { + var ( + accessToken string + err error + ) + if accessToken, err = r.GetAccessToken(); err != nil { + return nil, err + } + var response []byte + if response, err = util.PostJSON(fmt.Sprintf(createTagURL, accessToken), req); err != nil { + return nil, err + } + result := &CreateTagResponse{} + if err = util.DecodeWithError(response, result, "CreateTag"); err != nil { + return nil, err + } + return result, nil +} + +type ( + // UpdateTagRequest 更新标签名字请求 + UpdateTagRequest struct { + TagID int `json:"tagid"` + TagName string `json:"tagname"` + } +) + +// UpdateTag 更新标签名字 +// see https://developer.work.weixin.qq.com/document/path/90211 +func (r *Client) UpdateTag(req *UpdateTagRequest) 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(updateTagURL, accessToken), req); err != nil { + return err + } + return util.DecodeWithCommonError(response, "UpdateTag") +} + +// DeleteTag 删除标签 +// @see https://developer.work.weixin.qq.com/document/path/90212 +func (r *Client) DeleteTag(tagID 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(deleteTagURL, accessToken, tagID)); err != nil { + return err + } + return util.DecodeWithCommonError(response, "DeleteTag") +} From ca0b74e0827721c4d561f9003d8bba78551adc02 Mon Sep 17 00:00:00 2001 From: markwang <2951177317@qq.com> Date: Mon, 3 Apr 2023 17:06:07 +0800 Subject: [PATCH 15/27] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E4=BF=9D=E6=8C=81=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=A0=BC=E7=BB=9F?= =?UTF-8?q?=E4=B8=80,=E6=8E=A5=E5=8F=A3URL=E4=B8=8D=E5=AF=BC=E5=87=BA=20(#?= =?UTF-8?q?660)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 企业微信-客户联系-统计管理 * 企业微信-客户联系-统计管理 * 企业微信-客户联系-统计管理 * debug * rollback * debug * debug * 获取用户信息 * token * json.Marshal错误输出 * debug * bugfix * 企业微信-通讯录管理相关接口 * 企业微信-通讯录管理 * 企业微信-通讯录管理 * 企业微信-通讯录管理 * 企业微信-[联系我]方式新增和查询 * 企业微信-[联系我]方式新增和获取 * 企业微信-[联系我]方式更新 * 企业微信-[联系我]方式列表、删除 * json.Marshal错误输出 * 已实现接口bug修改 * 历史接口bugfix * 历史接口bugfix * comment * 企业微信:客户联系-消息推送;素材管理-上传图片 * fix * 企业微信-获取群发记录列表 * 历史接口bugfix * 1.企业微信-客户联系-消息推送-入群欢迎语素材管理 2.企业微信-通讯录管理-成员管理-获取成员ID列表 * golangci-lint * gofmt * 方法访问命名 * 企业微信-批量获取客户详情入参优化 * 企业微信-通讯录管理-标签管理-创建/更新/删除标签 * 请求地址常量无需导出 * 企业微信保持代码风格统一,接口URL不导出 --------- Co-authored-by: wang.yu Co-authored-by: markwang --- work/addresslist/department.go | 6 +-- work/addresslist/user.go | 18 ++++----- work/externalcontact/contact_way.go | 30 +++++++-------- work/externalcontact/external_user.go | 24 ++++++------ work/externalcontact/follow_user.go | 6 +-- work/externalcontact/groupchat.go | 10 ++--- work/externalcontact/join_way.go | 12 +++--- work/externalcontact/msg.go | 54 +++++++++++++-------------- work/externalcontact/statistic.go | 18 ++++----- work/externalcontact/tag.go | 30 +++++++-------- work/material/media.go | 13 +++---- work/robot/robot.go | 6 +-- 12 files changed, 113 insertions(+), 114 deletions(-) diff --git a/work/addresslist/department.go b/work/addresslist/department.go index 79a7e4611..cf2394662 100644 --- a/work/addresslist/department.go +++ b/work/addresslist/department.go @@ -7,8 +7,8 @@ import ( ) const ( - // DepartmentSimpleListURL 获取子部门ID列表 - DepartmentSimpleListURL = "https://qyapi.weixin.qq.com/cgi-bin/department/simplelist?access_token=%s&id=%d" + // departmentSimpleListURL 获取子部门ID列表 + departmentSimpleListURL = "https://qyapi.weixin.qq.com/cgi-bin/department/simplelist?access_token=%s&id=%d" ) type ( @@ -36,7 +36,7 @@ func (r *Client) DepartmentSimpleList(departmentID int) ([]*DepartmentID, error) return nil, err } var response []byte - if response, err = util.HTTPGet(fmt.Sprintf(DepartmentSimpleListURL, accessToken, departmentID)); err != nil { + if response, err = util.HTTPGet(fmt.Sprintf(departmentSimpleListURL, accessToken, departmentID)); err != nil { return nil, err } result := &DepartmentSimpleListResponse{} diff --git a/work/addresslist/user.go b/work/addresslist/user.go index 33e9fa185..4b6a88c4f 100644 --- a/work/addresslist/user.go +++ b/work/addresslist/user.go @@ -7,12 +7,12 @@ import ( ) const ( - // UserSimpleListURL 获取部门成员 - UserSimpleListURL = "https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?access_token=%s&department_id=%d" - // UserGetURL 读取成员 - UserGetURL = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s" - // UserListIDURL 获取成员ID列表 - UserListIDURL = "https://qyapi.weixin.qq.com/cgi-bin/user/list_id?access_token=%s" + // userSimpleListURL 获取部门成员 + userSimpleListURL = "https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?access_token=%s&department_id=%d" + // userGetURL 读取成员 + userGetURL = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s" + // userListIDURL 获取成员ID列表 + userListIDURL = "https://qyapi.weixin.qq.com/cgi-bin/user/list_id?access_token=%s" ) type ( @@ -41,7 +41,7 @@ func (r *Client) UserSimpleList(departmentID int) ([]*UserList, error) { return nil, err } var response []byte - if response, err = util.HTTPGet(fmt.Sprintf(UserSimpleListURL, accessToken, departmentID)); err != nil { + if response, err = util.HTTPGet(fmt.Sprintf(userSimpleListURL, accessToken, departmentID)); err != nil { return nil, err } result := &UserSimpleListResponse{} @@ -125,7 +125,7 @@ func (r *Client) UserGet(UserID string) (*UserGetResponse, error) { return nil, err } var response []byte - if response, err = util.HTTPGet(fmt.Sprintf(UserGetURL, accessToken, UserID)); err != nil { + if response, err = util.HTTPGet(fmt.Sprintf(userGetURL, accessToken, UserID)); err != nil { return nil, err } result := &UserGetResponse{} @@ -166,7 +166,7 @@ func (r *Client) UserListID(req *UserListIDRequest) (*UserListIDResponse, error) return nil, err } var response []byte - if response, err = util.PostJSON(fmt.Sprintf(UserListIDURL, accessToken), req); err != nil { + if response, err = util.PostJSON(fmt.Sprintf(userListIDURL, accessToken), req); err != nil { return nil, err } result := &UserListIDResponse{} diff --git a/work/externalcontact/contact_way.go b/work/externalcontact/contact_way.go index 5afdc19d9..150f05b97 100644 --- a/work/externalcontact/contact_way.go +++ b/work/externalcontact/contact_way.go @@ -7,16 +7,16 @@ import ( ) const ( - // AddContactWayURL 配置客户联系「联系我」方式 - AddContactWayURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_contact_way?access_token=%s" - // GetContactWayURL 获取企业已配置的「联系我」方式 - GetContactWayURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_contact_way?access_token=%s" - // UpdateContactWayURL 更新企业已配置的「联系我」方式 - UpdateContactWayURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/update_contact_way?access_token=%s" - // ListContactWayURL 获取企业已配置的「联系我」列表 - ListContactWayURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list_contact_way?access_token=%s" - // DelContactWayURL 删除企业已配置的「联系我」方式 - DelContactWayURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/del_contact_way?access_token=%s" + // addContactWayURL 配置客户联系「联系我」方式 + addContactWayURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_contact_way?access_token=%s" + // getContactWayURL 获取企业已配置的「联系我」方式 + getContactWayURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_contact_way?access_token=%s" + // updateContactWayURL 更新企业已配置的「联系我」方式 + updateContactWayURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/update_contact_way?access_token=%s" + // listContactWayURL 获取企业已配置的「联系我」列表 + listContactWayURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list_contact_way?access_token=%s" + // delContactWayURL 删除企业已配置的「联系我」方式 + delContactWayURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/del_contact_way?access_token=%s" ) type ( @@ -98,7 +98,7 @@ func (r *Client) AddContactWay(req *AddContactWayRequest) (*AddContactWayRespons return nil, err } var response []byte - if response, err = util.PostJSON(fmt.Sprintf(AddContactWayURL, accessToken), req); err != nil { + if response, err = util.PostJSON(fmt.Sprintf(addContactWayURL, accessToken), req); err != nil { return nil, err } result := &AddContactWayResponse{} @@ -149,7 +149,7 @@ func (r *Client) GetContactWay(req *GetContactWayRequest) (*GetContactWayRespons return nil, err } var response []byte - if response, err = util.PostJSON(fmt.Sprintf(GetContactWayURL, accessToken), req); err != nil { + if response, err = util.PostJSON(fmt.Sprintf(getContactWayURL, accessToken), req); err != nil { return nil, err } result := &GetContactWayResponse{} @@ -191,7 +191,7 @@ func (r *Client) UpdateContactWay(req *UpdateContactWayRequest) (*UpdateContactW return nil, err } var response []byte - if response, err = util.PostJSON(fmt.Sprintf(UpdateContactWayURL, accessToken), req); err != nil { + if response, err = util.PostJSON(fmt.Sprintf(updateContactWayURL, accessToken), req); err != nil { return nil, err } result := &UpdateContactWayResponse{} @@ -232,7 +232,7 @@ func (r *Client) ListContactWay(req *ListContactWayRequest) (*ListContactWayResp return nil, err } var response []byte - if response, err = util.PostJSON(fmt.Sprintf(ListContactWayURL, accessToken), req); err != nil { + if response, err = util.PostJSON(fmt.Sprintf(listContactWayURL, accessToken), req); err != nil { return nil, err } result := &ListContactWayResponse{} @@ -264,7 +264,7 @@ func (r *Client) DelContactWay(req *DelContactWayRequest) (*DelContactWayRespons return nil, err } var response []byte - if response, err = util.PostJSON(fmt.Sprintf(DelContactWayURL, accessToken), req); err != nil { + if response, err = util.PostJSON(fmt.Sprintf(delContactWayURL, accessToken), req); err != nil { return nil, err } result := &DelContactWayResponse{} diff --git a/work/externalcontact/external_user.go b/work/externalcontact/external_user.go index b8d8f9c63..3dd2e9e4d 100644 --- a/work/externalcontact/external_user.go +++ b/work/externalcontact/external_user.go @@ -8,14 +8,14 @@ import ( ) const ( - // FetchExternalContactUserListURL 获取客户列表 - FetchExternalContactUserListURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list" - // FetchExternalContactUserDetailURL 获取客户详情 - FetchExternalContactUserDetailURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get" - // FetchBatchExternalContactUserDetailURL 批量获取客户详情 - FetchBatchExternalContactUserDetailURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/batch/get_by_user" - // UpdateUserRemarkURL 更新客户备注信息 - UpdateUserRemarkURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/remark" + // fetchExternalContactUserListURL 获取客户列表 + fetchExternalContactUserListURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list" + // fetchExternalContactUserDetailURL 获取客户详情 + fetchExternalContactUserDetailURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get" + // fetchBatchExternalContactUserDetailURL 批量获取客户详情 + fetchBatchExternalContactUserDetailURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/batch/get_by_user" + // updateUserRemarkURL 更新客户备注信息 + updateUserRemarkURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/remark" ) // ExternalUserListResponse 外部联系人列表响应 @@ -32,7 +32,7 @@ func (r *Client) GetExternalUserList(userID string) ([]string, error) { return nil, err } var response []byte - response, err = util.HTTPGet(fmt.Sprintf("%s?access_token=%v&userid=%v", FetchExternalContactUserListURL, accessToken, userID)) + response, err = util.HTTPGet(fmt.Sprintf("%s?access_token=%v&userid=%v", fetchExternalContactUserListURL, accessToken, userID)) if err != nil { return nil, err } @@ -107,7 +107,7 @@ func (r *Client) GetExternalUserDetail(externalUserID string, nextCursor ...stri if len(nextCursor) > 0 { cursor = nextCursor[0] } - response, err = util.HTTPGet(fmt.Sprintf("%s?access_token=%v&external_userid=%v&cursor=%v", FetchExternalContactUserDetailURL, accessToken, externalUserID, cursor)) + response, err = util.HTTPGet(fmt.Sprintf("%s?access_token=%v&external_userid=%v&cursor=%v", fetchExternalContactUserDetailURL, accessToken, externalUserID, cursor)) if err != nil { return nil, err } @@ -178,7 +178,7 @@ func (r *Client) BatchGetExternalUserDetails(request BatchGetExternalUserDetails if err != nil { return nil, err } - response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", FetchBatchExternalContactUserDetailURL, accessToken), string(jsonData)) + response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", fetchBatchExternalContactUserDetailURL, accessToken), string(jsonData)) if err != nil { return nil, err } @@ -213,7 +213,7 @@ func (r *Client) UpdateUserRemark(request UpdateUserRemarkRequest) error { if err != nil { return err } - response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", UpdateUserRemarkURL, accessToken), string(jsonData)) + response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", updateUserRemarkURL, accessToken), string(jsonData)) if err != nil { return err } diff --git a/work/externalcontact/follow_user.go b/work/externalcontact/follow_user.go index 1cfc3616f..67469b8e8 100644 --- a/work/externalcontact/follow_user.go +++ b/work/externalcontact/follow_user.go @@ -7,8 +7,8 @@ import ( ) const ( - // FetchFollowUserListURL 获取配置了客户联系功能的成员列表 - FetchFollowUserListURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_follow_user_list" + // fetchFollowUserListURL 获取配置了客户联系功能的成员列表 + fetchFollowUserListURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_follow_user_list" ) // followerUserResponse 客户联系功能的成员列表响应 @@ -25,7 +25,7 @@ func (r *Client) GetFollowUserList() ([]string, error) { return nil, err } var response []byte - response, err = util.HTTPGet(fmt.Sprintf("%s?access_token=%s", FetchFollowUserListURL, accessToken)) + response, err = util.HTTPGet(fmt.Sprintf("%s?access_token=%s", fetchFollowUserListURL, accessToken)) if err != nil { return nil, err } diff --git a/work/externalcontact/groupchat.go b/work/externalcontact/groupchat.go index 0aa9ccd5b..e0985306f 100644 --- a/work/externalcontact/groupchat.go +++ b/work/externalcontact/groupchat.go @@ -6,8 +6,8 @@ import ( "github.com/silenceper/wechat/v2/util" ) -// OpengIDToChatIDURL 客户群opengid转换URL -const OpengIDToChatIDURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/opengid_to_chatid" +// opengIDToChatIDURL 客户群opengid转换URL +const opengIDToChatIDURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/opengid_to_chatid" type ( //GroupChatListRequest 获取客户群列表的请求参数 @@ -39,7 +39,7 @@ func (r *Client) GetGroupChatList(req *GroupChatListRequest) (*GroupChatListResp return nil, err } var response []byte - response, err = util.PostJSON(fmt.Sprintf("%s/list?access_token=%s", GroupChatURL, accessToken), req) + response, err = util.PostJSON(fmt.Sprintf("%s/list?access_token=%s", groupChatURL, accessToken), req) if err != nil { return nil, err } @@ -100,7 +100,7 @@ func (r *Client) GetGroupChatDetail(req *GroupChatDetailRequest) (*GroupChatDeta return nil, err } var response []byte - response, err = util.PostJSON(fmt.Sprintf("%s/get?access_token=%s", GroupChatURL, accessToken), req) + response, err = util.PostJSON(fmt.Sprintf("%s/get?access_token=%s", groupChatURL, accessToken), req) if err != nil { return nil, err } @@ -131,7 +131,7 @@ func (r *Client) OpengIDToChatID(req *OpengIDToChatIDRequest) (*OpengIDToChatIDR return nil, err } var response []byte - response, err = util.PostJSON(fmt.Sprintf("%s?access_token=%s", OpengIDToChatIDURL, accessToken), req) + response, err = util.PostJSON(fmt.Sprintf("%s?access_token=%s", opengIDToChatIDURL, accessToken), req) if err != nil { return nil, err } diff --git a/work/externalcontact/join_way.go b/work/externalcontact/join_way.go index 239610369..818a8ca7e 100644 --- a/work/externalcontact/join_way.go +++ b/work/externalcontact/join_way.go @@ -6,8 +6,8 @@ import ( "github.com/silenceper/wechat/v2/util" ) -// GroupChatURL 客户群 -const GroupChatURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat" +// groupChatURL 客户群 +const groupChatURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat" type ( // AddJoinWayRequest 添加群配置请求参数 @@ -39,7 +39,7 @@ func (r *Client) AddJoinWay(req *AddJoinWayRequest) (*AddJoinWayResponse, error) if accessToken, err = r.GetAccessToken(); err != nil { return nil, err } - response, err = util.PostJSON(fmt.Sprintf("%s/add_join_way?access_token=%s", GroupChatURL, accessToken), req) + response, err = util.PostJSON(fmt.Sprintf("%s/add_join_way?access_token=%s", groupChatURL, accessToken), req) if err != nil { return nil, err } @@ -86,7 +86,7 @@ func (r *Client) GetJoinWay(req *JoinWayConfigRequest) (*GetJoinWayResponse, err if accessToken, err = r.GetAccessToken(); err != nil { return nil, err } - response, err = util.PostJSON(fmt.Sprintf("%s/get_join_way?access_token=%s", GroupChatURL, accessToken), req) + response, err = util.PostJSON(fmt.Sprintf("%s/get_join_way?access_token=%s", groupChatURL, accessToken), req) if err != nil { return nil, err } @@ -120,7 +120,7 @@ func (r *Client) UpdateJoinWay(req *UpdateJoinWayRequest) error { if accessToken, err = r.GetAccessToken(); err != nil { return err } - response, err = util.PostJSON(fmt.Sprintf("%s/update_join_way?access_token=%s", GroupChatURL, accessToken), req) + response, err = util.PostJSON(fmt.Sprintf("%s/update_join_way?access_token=%s", groupChatURL, accessToken), req) if err != nil { return err } @@ -138,7 +138,7 @@ func (r *Client) DelJoinWay(req *JoinWayConfigRequest) error { if accessToken, err = r.GetAccessToken(); err != nil { return err } - response, err = util.PostJSON(fmt.Sprintf("%s/del_join_way?access_token=%s", GroupChatURL, accessToken), req) + response, err = util.PostJSON(fmt.Sprintf("%s/del_join_way?access_token=%s", groupChatURL, accessToken), req) if err != nil { return err } diff --git a/work/externalcontact/msg.go b/work/externalcontact/msg.go index 4d72ab46e..5c2f90144 100644 --- a/work/externalcontact/msg.go +++ b/work/externalcontact/msg.go @@ -7,24 +7,24 @@ import ( ) const ( - // AddMsgTemplateURL 创建企业群发 - AddMsgTemplateURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_msg_template?access_token=%s" - // GetGroupMsgListV2URL 获取群发记录列表 - GetGroupMsgListV2URL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_groupmsg_list_v2?access_token=%s" - // GetGroupMsgTaskURL 获取群发成员发送任务列表 - GetGroupMsgTaskURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_groupmsg_task?access_token=%s" - // GetGroupMsgSendResultURL 获取企业群发成员执行结果 - GetGroupMsgSendResultURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_groupmsg_send_result?access_token=%s" - // SendWelcomeMsgURL 发送新客户欢迎语 - SendWelcomeMsgURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/send_welcome_msg?access_token=%s" - // AddGroupWelcomeTemplateURL 添加入群欢迎语素材 - AddGroupWelcomeTemplateURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/add?access_token=%s" - // EditGroupWelcomeTemplateURL 编辑入群欢迎语素材 - EditGroupWelcomeTemplateURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/edit?access_token=%s" - // GetGroupWelcomeTemplateURL 获取入群欢迎语素材 - GetGroupWelcomeTemplateURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/get?access_token=%s" - // DelGroupWelcomeTemplateURL 删除入群欢迎语素材 - DelGroupWelcomeTemplateURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/del?access_token=%s" + // addMsgTemplateURL 创建企业群发 + addMsgTemplateURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_msg_template?access_token=%s" + // getGroupMsgListV2URL 获取群发记录列表 + getGroupMsgListV2URL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_groupmsg_list_v2?access_token=%s" + // getGroupMsgTaskURL 获取群发成员发送任务列表 + getGroupMsgTaskURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_groupmsg_task?access_token=%s" + // getGroupMsgSendResultURL 获取企业群发成员执行结果 + getGroupMsgSendResultURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_groupmsg_send_result?access_token=%s" + // sendWelcomeMsgURL 发送新客户欢迎语 + sendWelcomeMsgURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/send_welcome_msg?access_token=%s" + // addGroupWelcomeTemplateURL 添加入群欢迎语素材 + addGroupWelcomeTemplateURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/add?access_token=%s" + // editGroupWelcomeTemplateURL 编辑入群欢迎语素材 + editGroupWelcomeTemplateURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/edit?access_token=%s" + // getGroupWelcomeTemplateURL 获取入群欢迎语素材 + getGroupWelcomeTemplateURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/get?access_token=%s" + // delGroupWelcomeTemplateURL 删除入群欢迎语素材 + delGroupWelcomeTemplateURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/del?access_token=%s" ) // AddMsgTemplateRequest 创建企业群发请求 @@ -98,7 +98,7 @@ func (r *Client) AddMsgTemplate(req *AddMsgTemplateRequest) (*AddMsgTemplateResp return nil, err } var response []byte - if response, err = util.PostJSON(fmt.Sprintf(AddMsgTemplateURL, accessToken), req); err != nil { + if response, err = util.PostJSON(fmt.Sprintf(addMsgTemplateURL, accessToken), req); err != nil { return nil, err } result := &AddMsgTemplateResponse{} @@ -147,7 +147,7 @@ func (r *Client) GetGroupMsgListV2(req *GetGroupMsgListV2Request) (*GetGroupMsgL return nil, err } var response []byte - if response, err = util.PostJSON(fmt.Sprintf(GetGroupMsgListV2URL, accessToken), req); err != nil { + if response, err = util.PostJSON(fmt.Sprintf(getGroupMsgListV2URL, accessToken), req); err != nil { return nil, err } result := &GetGroupMsgListV2Response{} @@ -189,7 +189,7 @@ func (r *Client) GetGroupMsgTask(req *GetGroupMsgTaskRequest) (*GetGroupMsgTaskR return nil, err } var response []byte - if response, err = util.PostJSON(fmt.Sprintf(GetGroupMsgTaskURL, accessToken), req); err != nil { + if response, err = util.PostJSON(fmt.Sprintf(getGroupMsgTaskURL, accessToken), req); err != nil { return nil, err } result := &GetGroupMsgTaskResponse{} @@ -234,7 +234,7 @@ func (r *Client) GetGroupMsgSendResult(req *GetGroupMsgSendResultRequest) (*GetG return nil, err } var response []byte - if response, err = util.PostJSON(fmt.Sprintf(GetGroupMsgSendResultURL, accessToken), req); err != nil { + if response, err = util.PostJSON(fmt.Sprintf(getGroupMsgSendResultURL, accessToken), req); err != nil { return nil, err } result := &GetGroupMsgSendResultResponse{} @@ -267,7 +267,7 @@ func (r *Client) SendWelcomeMsg(req *SendWelcomeMsgRequest) error { return err } var response []byte - if response, err = util.PostJSON(fmt.Sprintf(SendWelcomeMsgURL, accessToken), req); err != nil { + if response, err = util.PostJSON(fmt.Sprintf(sendWelcomeMsgURL, accessToken), req); err != nil { return err } result := &SendWelcomeMsgResponse{} @@ -306,7 +306,7 @@ func (r *Client) AddGroupWelcomeTemplate(req *AddGroupWelcomeTemplateRequest) (* return nil, err } var response []byte - if response, err = util.PostJSON(fmt.Sprintf(AddGroupWelcomeTemplateURL, accessToken), req); err != nil { + if response, err = util.PostJSON(fmt.Sprintf(addGroupWelcomeTemplateURL, accessToken), req); err != nil { return nil, err } result := &AddGroupWelcomeTemplateResponse{} @@ -344,7 +344,7 @@ func (r *Client) EditGroupWelcomeTemplate(req *EditGroupWelcomeTemplateRequest) return err } var response []byte - if response, err = util.PostJSON(fmt.Sprintf(EditGroupWelcomeTemplateURL, accessToken), req); err != nil { + if response, err = util.PostJSON(fmt.Sprintf(editGroupWelcomeTemplateURL, accessToken), req); err != nil { return err } result := &EditGroupWelcomeTemplateResponse{} @@ -381,7 +381,7 @@ func (r *Client) GetGroupWelcomeTemplate(req *GetGroupWelcomeTemplateRequest) (* return nil, err } var response []byte - if response, err = util.PostJSON(fmt.Sprintf(GetGroupWelcomeTemplateURL, accessToken), req); err != nil { + if response, err = util.PostJSON(fmt.Sprintf(getGroupWelcomeTemplateURL, accessToken), req); err != nil { return nil, err } result := &GetGroupWelcomeTemplateResponse{} @@ -413,7 +413,7 @@ func (r *Client) DelGroupWelcomeTemplate(req *DelGroupWelcomeTemplateRequest) er return err } var response []byte - if response, err = util.PostJSON(fmt.Sprintf(DelGroupWelcomeTemplateURL, accessToken), req); err != nil { + if response, err = util.PostJSON(fmt.Sprintf(delGroupWelcomeTemplateURL, accessToken), req); err != nil { return err } result := &DelGroupWelcomeTemplateResponse{} diff --git a/work/externalcontact/statistic.go b/work/externalcontact/statistic.go index e308f664f..2e00ae2ba 100644 --- a/work/externalcontact/statistic.go +++ b/work/externalcontact/statistic.go @@ -8,12 +8,12 @@ import ( ) const ( - // GetUserBehaviorDataURL 获取「联系客户统计」数据 - GetUserBehaviorDataURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_user_behavior_data" - // GetGroupChatStatURL 获取「群聊数据统计」数据 按群主聚合的方式 - GetGroupChatStatURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat/statistic" - // GetGroupChatStatByDayURL 获取「群聊数据统计」数据 按自然日聚合的方式 - GetGroupChatStatByDayURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat/statistic_group_by_day" + // getUserBehaviorDataURL 获取「联系客户统计」数据 + getUserBehaviorDataURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_user_behavior_data" + // getGroupChatStatURL 获取「群聊数据统计」数据 按群主聚合的方式 + getGroupChatStatURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat/statistic" + // getGroupChatStatByDayURL 获取「群聊数据统计」数据 按自然日聚合的方式 + getGroupChatStatByDayURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat/statistic_group_by_day" ) type ( @@ -54,7 +54,7 @@ func (r *Client) GetUserBehaviorData(req *GetUserBehaviorRequest) ([]BehaviorDat if err != nil { return nil, err } - response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", GetUserBehaviorDataURL, accessToken), string(jsonData)) + response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", getUserBehaviorDataURL, accessToken), string(jsonData)) if err != nil { return nil, err } @@ -120,7 +120,7 @@ func (r *Client) GetGroupChatStat(req *GetGroupChatStatRequest) (*GetGroupChatSt if err != nil { return nil, err } - response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", GetGroupChatStatURL, accessToken), string(jsonData)) + response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", getGroupChatStatURL, accessToken), string(jsonData)) if err != nil { return nil, err } @@ -163,7 +163,7 @@ func (r *Client) GetGroupChatStatByDay(req *GetGroupChatStatByDayRequest) ([]Get if err != nil { return nil, err } - response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", GetGroupChatStatByDayURL, accessToken), string(jsonData)) + response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", getGroupChatStatByDayURL, accessToken), string(jsonData)) if err != nil { return nil, err } diff --git a/work/externalcontact/tag.go b/work/externalcontact/tag.go index 609501042..ce1ac9290 100644 --- a/work/externalcontact/tag.go +++ b/work/externalcontact/tag.go @@ -8,16 +8,16 @@ import ( ) const ( - // GetCropTagURL 获取标签列表 - GetCropTagURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_corp_tag_list" - // AddCropTagURL 添加标签 - AddCropTagURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_corp_tag" - // EditCropTagURL 修改标签 - EditCropTagURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/edit_corp_tag" - // DelCropTagURL 删除标签 - DelCropTagURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/del_corp_tag" - // MarkCropTagURL 为客户打上、删除标签 - MarkCropTagURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/mark_tag" + // getCropTagURL 获取标签列表 + getCropTagURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_corp_tag_list" + // addCropTagURL 添加标签 + addCropTagURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_corp_tag" + // editCropTagURL 修改标签 + editCropTagURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/edit_corp_tag" + // delCropTagURL 删除标签 + delCropTagURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/del_corp_tag" + // markCropTagURL 为客户打上、删除标签 + markCropTagURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/mark_tag" ) // GetCropTagRequest 获取企业标签请求 @@ -63,7 +63,7 @@ func (r *Client) GetCropTagList(req GetCropTagRequest) ([]TagGroup, error) { if err != nil { return nil, err } - response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", GetCropTagURL, accessToken), string(jsonData)) + response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", getCropTagURL, accessToken), string(jsonData)) if err != nil { return nil, err } @@ -109,7 +109,7 @@ func (r *Client) AddCropTag(req AddCropTagRequest) (*TagGroup, error) { if err != nil { return nil, err } - response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", AddCropTagURL, accessToken), string(jsonData)) + response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", addCropTagURL, accessToken), string(jsonData)) if err != nil { return nil, err } @@ -141,7 +141,7 @@ func (r *Client) EditCropTag(req EditCropTagRequest) error { if err != nil { return err } - response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", EditCropTagURL, accessToken), string(jsonData)) + response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", editCropTagURL, accessToken), string(jsonData)) if err != nil { return err } @@ -167,7 +167,7 @@ func (r *Client) DeleteCropTag(req DeleteCropTagRequest) error { if err != nil { return err } - response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", DelCropTagURL, accessToken), string(jsonData)) + response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", delCropTagURL, accessToken), string(jsonData)) if err != nil { return err } @@ -195,7 +195,7 @@ func (r *Client) MarkTag(request MarkTagRequest) error { if err != nil { return err } - response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", MarkCropTagURL, accessToken), string(jsonData)) + response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", markCropTagURL, accessToken), string(jsonData)) if err != nil { return err } diff --git a/work/material/media.go b/work/material/media.go index cb37344c7..1217e9cf3 100644 --- a/work/material/media.go +++ b/work/material/media.go @@ -7,11 +7,10 @@ import ( ) const ( - // UploadImgURL 上传图片 - UploadImgURL = "https://qyapi.weixin.qq.com/cgi-bin/media/uploadimg?access_token=%s" - - // UploadTempFile 上传临时素材 - UploadTempFile = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s" + // uploadImgURL 上传图片 + uploadImgURL = "https://qyapi.weixin.qq.com/cgi-bin/media/uploadimg?access_token=%s" + // uploadTempFile 上传临时素材 + uploadTempFile = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s" ) // UploadImgResponse 上传图片响应 @@ -39,7 +38,7 @@ func (r *Client) UploadImg(filename string) (*UploadImgResponse, error) { return nil, err } var response []byte - if response, err = util.PostFile("media", filename, fmt.Sprintf(UploadImgURL, accessToken)); err != nil { + if response, err = util.PostFile("media", filename, fmt.Sprintf(uploadImgURL, accessToken)); err != nil { return nil, err } result := &UploadImgResponse{} @@ -61,7 +60,7 @@ func (r *Client) UploadTempFile(filename string, mediaType string) (*UploadTempF return nil, err } var response []byte - if response, err = util.PostFile("media", filename, fmt.Sprintf(UploadTempFile, accessToken, mediaType)); err != nil { + if response, err = util.PostFile("media", filename, fmt.Sprintf(uploadTempFile, accessToken, mediaType)); err != nil { return nil, err } result := &UploadTempFileResponse{} diff --git a/work/robot/robot.go b/work/robot/robot.go index 145f63d4d..eee38377e 100644 --- a/work/robot/robot.go +++ b/work/robot/robot.go @@ -8,15 +8,15 @@ import ( ) const ( - // WebhookSendURL 机器人发送群组消息 - WebhookSendURL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=%s" + // webhookSendURL 机器人发送群组消息 + webhookSendURL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=%s" ) // RobotBroadcast 群机器人消息发送 // @see https://developer.work.weixin.qq.com/document/path/91770 func (r *Client) RobotBroadcast(webhookKey string, options interface{}) (info util.CommonError, err error) { var data []byte - if data, err = util.PostJSON(fmt.Sprintf(WebhookSendURL, webhookKey), options); err != nil { + if data, err = util.PostJSON(fmt.Sprintf(webhookSendURL, webhookKey), options); err != nil { return } if err = json.Unmarshal(data, &info); err != nil { From 8bc1474777822d1cbde0d0350438e236eb4c26b5 Mon Sep 17 00:00:00 2001 From: Guo yongrong Date: Mon, 3 Apr 2023 18:13:25 +0800 Subject: [PATCH 16/27] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8F=91=E9=80=81?= =?UTF-8?q?=E5=9B=BE=E6=96=87=E6=B6=88=E6=81=AF=EF=BC=88=E7=82=B9=E5=87=BB?= =?UTF-8?q?=E8=B7=B3=E8=BD=AC=E5=88=B0=E5=9B=BE=E6=96=87=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=EF=BC=89=E4=BD=BF=E7=94=A8=E9=80=9A=E8=BF=87?= =?UTF-8?q?=20=E2=80=9C=E5=8F=91=E5=B8=83=E2=80=9D=20=E7=B3=BB=E5=88=97?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=BE=97=E5=88=B0=E7=9A=84=20article=5Fid=20?= =?UTF-8?q?(#664)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: handson@intonead.com --- officialaccount/message/customer_message.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/officialaccount/message/customer_message.go b/officialaccount/message/customer_message.go index 67acc8a01..6742a61dd 100644 --- a/officialaccount/message/customer_message.go +++ b/officialaccount/message/customer_message.go @@ -38,6 +38,7 @@ type CustomerMessage struct { Wxcard *MediaWxcard `json:"wxcard,omitempty"` // 可选 Msgmenu *MediaMsgmenu `json:"msgmenu,omitempty"` // 可选 Miniprogrampage *MediaMiniprogrampage `json:"miniprogrampage,omitempty"` // 可选 + Mpnewsarticle *MediaArticle `json:"mpnewsarticle,omitempty"` // 可选 } // NewCustomerTextMessage 文本消息结构体构造方法 @@ -97,6 +98,11 @@ type MediaResource struct { MediaID string `json:"media_id"` } +// MediaArticle 消息使用的已发布文章id +type MediaArticle struct { + ArticleID string `json:"article_id"` +} + // MediaVideo 视频消息包含的内容 type MediaVideo struct { MediaID string `json:"media_id"` From 01784c2a4a7daac4b429dba710ad3d2075f021fa Mon Sep 17 00:00:00 2001 From: Ace Date: Mon, 3 Apr 2023 20:31:58 +0800 Subject: [PATCH 17/27] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E4=BA=92=E9=80=9A?= =?UTF-8?q?=E7=BA=A2=E5=8C=85=E5=92=8C=E8=A7=86=E9=A2=91=E5=8F=B7=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E9=83=A8=E5=88=86=E5=AD=97=E6=AE=B5=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E7=9A=84=E6=95=B0=E6=8D=AE=E7=B1=BB=E5=9E=8B=E5=A3=B0=E6=98=8E?= =?UTF-8?q?=20(#663)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix:修复消息存档内红包祝福语和视频号部分字段数据类型声明错误的问题 * docs:更新注释说明 * fix:校正互通红包内的数据结构声明类型 --- work/msgaudit/message.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/work/msgaudit/message.go b/work/msgaudit/message.go index 91202de5b..0b4a33447 100644 --- a/work/msgaudit/message.go +++ b/work/msgaudit/message.go @@ -150,7 +150,7 @@ type TodoMessage struct { BaseMessage Todo struct { Title string `json:"title,omitempty"` // 代办的来源文本 - Content string `json:"content,omitempty"` // 代办的具体内容 + Content string `json:"content,omitempty"` // 代办的具体内容 } `json:"todo,omitempty"` } @@ -266,10 +266,10 @@ type VoipDocShareMessage struct { type ExternalRedPacketMessage struct { BaseMessage RedPacket struct { - Type int32 `json:"type,omitempty"` // 红包消息类型。1 普通红包、2 拼手气群红包。Uint32类型 - Wish int32 `json:"wish,omitempty"` // 红包祝福语。String类型 - TotalCnt int32 `json:"totalcnt,omitempty"` // 红包总个数。Uint32类型 - TotalAmount int32 `json:"totalamount,omitempty"` // 红包消息类型。1 普通红包、2 拼手气群红包。Uint32类型 + Type uint32 `json:"type,omitempty"` // 红包消息类型。1 普通红包、2 拼手气群红包。Uint32类型 + Wish string `json:"wish,omitempty"` // 红包祝福语。String类型 + TotalCnt uint32 `json:"totalcnt,omitempty"` // 红包总个数。Uint32类型 + TotalAmount uint32 `json:"totalamount,omitempty"` // 红包总金额。Uint32类型,单位为分。 } `json:"redpacket,omitempty"` } @@ -277,9 +277,9 @@ type ExternalRedPacketMessage struct { type SphFeedMessage struct { BaseMessage SphFeed struct { - FeedType string `json:"feed_type,omitempty"` // 视频号消息类型 - SphName string `json:"sph_name,omitempty"` // 视频号账号名称 - FeedDesc uint64 `json:"feed_desc,omitempty"` // 视频号账号名称 + FeedType uint32 `json:"feed_type,omitempty"` // 视频号消息类型。2 图片、4 视频、9 直播。Uint32类型 + SphName string `json:"sph_name,omitempty"` // 视频号账号名称。String类型 + FeedDesc string `json:"feed_desc,omitempty"` // 视频号消息描述。String类型 } } From 07b7dc40fc748abb8498059f129408b647ccad29 Mon Sep 17 00:00:00 2001 From: okhowang <3352585+okhowang@users.noreply.github.com> Date: Mon, 3 Apr 2023 20:32:44 +0800 Subject: [PATCH 18/27] =?UTF-8?q?cache=E5=A2=9E=E5=8A=A0=E5=B8=A6Context?= =?UTF-8?q?=E7=89=88=E6=9C=AC=EF=BC=8C=E5=BC=80=E6=94=BE=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3=E6=94=AF=E6=8C=81Context?= =?UTF-8?q?=E7=89=88=E6=9C=AC=20(#653)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cache/cache.go | 46 ++++++++++- cache/redis.go | 28 ++++++- cache/redis_test.go | 10 ++- go.mod | 1 + go.sum | 7 ++ openplatform/context/accessToken.go | 113 ++++++++++++++++++++-------- util/http.go | 16 +++- 7 files changed, 178 insertions(+), 43 deletions(-) diff --git a/cache/cache.go b/cache/cache.go index f3feb84c4..077597dbe 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -1,6 +1,9 @@ package cache -import "time" +import ( + "context" + "time" +) // Cache interface type Cache interface { @@ -9,3 +12,44 @@ type Cache interface { IsExist(key string) bool Delete(key string) error } + +// ContextCache interface +type ContextCache interface { + Cache + GetContext(ctx context.Context, key string) interface{} + SetContext(ctx context.Context, key string, val interface{}, timeout time.Duration) error + IsExistContext(ctx context.Context, key string) bool + DeleteContext(ctx context.Context, key string) error +} + +// GetContext get value from cache +func GetContext(ctx context.Context, cache Cache, key string) interface{} { + if cache, ok := cache.(ContextCache); ok { + return cache.GetContext(ctx, key) + } + return cache.Get(key) +} + +// SetContext set value to cache +func SetContext(ctx context.Context, cache Cache, key string, val interface{}, timeout time.Duration) error { + if cache, ok := cache.(ContextCache); ok { + return cache.SetContext(ctx, key, val, timeout) + } + return cache.Set(key, val, timeout) +} + +// IsExistContext check value exists in cache. +func IsExistContext(ctx context.Context, cache Cache, key string) bool { + if cache, ok := cache.(ContextCache); ok { + return cache.IsExistContext(ctx, key) + } + return cache.IsExist(key) +} + +// DeleteContext delete value in cache. +func DeleteContext(ctx context.Context, cache Cache, key string) error { + if cache, ok := cache.(ContextCache); ok { + return cache.DeleteContext(ctx, key) + } + return cache.Delete(key) +} diff --git a/cache/redis.go b/cache/redis.go index 24a736f94..f51f7bf88 100644 --- a/cache/redis.go +++ b/cache/redis.go @@ -47,7 +47,12 @@ func (r *Redis) SetRedisCtx(ctx context.Context) { // Get 获取一个值 func (r *Redis) Get(key string) interface{} { - result, err := r.conn.Do(r.ctx, "GET", key).Result() + return r.GetContext(r.ctx, key) +} + +// GetContext 获取一个值 +func (r *Redis) GetContext(ctx context.Context, key string) interface{} { + result, err := r.conn.Do(ctx, "GET", key).Result() if err != nil { return nil } @@ -56,17 +61,32 @@ func (r *Redis) Get(key string) interface{} { // Set 设置一个值 func (r *Redis) Set(key string, val interface{}, timeout time.Duration) error { - return r.conn.SetEX(r.ctx, key, val, timeout).Err() + return r.SetContext(r.ctx, key, val, timeout) +} + +// SetContext 设置一个值 +func (r *Redis) SetContext(ctx context.Context, key string, val interface{}, timeout time.Duration) error { + return r.conn.SetEX(ctx, key, val, timeout).Err() } // IsExist 判断key是否存在 func (r *Redis) IsExist(key string) bool { - result, _ := r.conn.Exists(r.ctx, key).Result() + return r.IsExistContext(r.ctx, key) +} + +// IsExistContext 判断key是否存在 +func (r *Redis) IsExistContext(ctx context.Context, key string) bool { + result, _ := r.conn.Exists(ctx, key).Result() return result > 0 } // Delete 删除 func (r *Redis) Delete(key string) error { - return r.conn.Del(r.ctx, key).Err() + return r.DeleteContext(r.ctx, key) +} + +// DeleteContext 删除 +func (r *Redis) DeleteContext(ctx context.Context, key string) error { + return r.conn.Del(ctx, key).Err() } diff --git a/cache/redis_test.go b/cache/redis_test.go index 8973fe6a0..a41a2f166 100644 --- a/cache/redis_test.go +++ b/cache/redis_test.go @@ -4,17 +4,23 @@ import ( "context" "testing" "time" + + "github.com/alicebob/miniredis/v2" ) func TestRedis(t *testing.T) { + server, err := miniredis.Run() + if err != nil { + t.Error("miniredis.Run Error", err) + } + t.Cleanup(server.Close) var ( timeoutDuration = time.Second ctx = context.Background() opts = &RedisOpts{ - Host: "127.0.0.1:6379", + Host: server.Addr(), } redis = NewRedis(ctx, opts) - err error val = "silenceper" key = "username" ) diff --git a/go.mod b/go.mod index b49244c3a..0180599f0 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/silenceper/wechat/v2 go 1.16 require ( + github.com/alicebob/miniredis/v2 v2.30.0 github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d github.com/fatih/structs v1.1.0 github.com/go-redis/redis/v8 v8.11.5 diff --git a/go.sum b/go.sum index f9efc2738..64deda94f 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,7 @@ +github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= +github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/miniredis/v2 v2.30.0 h1:uA3uhDbCxfO9+DI/DuGeAMr9qI+noVWwGPNTFuKID5M= +github.com/alicebob/miniredis/v2 v2.30.0/go.mod h1:84TWKZlxYkfgMucPBf5SOQBYJceZeQRFIaQgNMiCX6Q= github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw= github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= @@ -71,6 +75,8 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ= +github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -89,6 +95,7 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/openplatform/context/accessToken.go b/openplatform/context/accessToken.go index a8316e2ef..68861295f 100644 --- a/openplatform/context/accessToken.go +++ b/openplatform/context/accessToken.go @@ -2,11 +2,13 @@ package context import ( + "context" "encoding/json" "fmt" "net/url" "time" + "github.com/silenceper/wechat/v2/cache" "github.com/silenceper/wechat/v2/util" ) @@ -31,24 +33,29 @@ type ComponentAccessToken struct { ExpiresIn int64 `json:"expires_in"` } -// GetComponentAccessToken 获取 ComponentAccessToken -func (ctx *Context) GetComponentAccessToken() (string, error) { +// GetComponentAccessTokenContext 获取 ComponentAccessToken +func (ctx *Context) GetComponentAccessTokenContext(stdCtx context.Context) (string, error) { accessTokenCacheKey := fmt.Sprintf("component_access_token_%s", ctx.AppID) - val := ctx.Cache.Get(accessTokenCacheKey) + val := cache.GetContext(stdCtx, ctx.Cache, accessTokenCacheKey) if val == nil { return "", fmt.Errorf("cann't get component access token") } return val.(string), nil } -// SetComponentAccessToken 通过component_verify_ticket 获取 ComponentAccessToken -func (ctx *Context) SetComponentAccessToken(verifyTicket string) (*ComponentAccessToken, error) { +// GetComponentAccessToken 获取 ComponentAccessToken +func (ctx *Context) GetComponentAccessToken() (string, error) { + return ctx.GetComponentAccessTokenContext(context.Background()) +} + +// SetComponentAccessTokenContext 通过component_verify_ticket 获取 ComponentAccessToken +func (ctx *Context) SetComponentAccessTokenContext(stdCtx context.Context, verifyTicket string) (*ComponentAccessToken, error) { body := map[string]string{ "component_appid": ctx.AppID, "component_appsecret": ctx.AppSecret, "component_verify_ticket": verifyTicket, } - respBody, err := util.PostJSON(componentAccessTokenURL, body) + respBody, err := util.PostJSONContext(stdCtx, componentAccessTokenURL, body) if err != nil { return nil, err } @@ -64,15 +71,20 @@ func (ctx *Context) SetComponentAccessToken(verifyTicket string) (*ComponentAcce accessTokenCacheKey := fmt.Sprintf("component_access_token_%s", ctx.AppID) expires := at.ExpiresIn - 1500 - if err := ctx.Cache.Set(accessTokenCacheKey, at.AccessToken, time.Duration(expires)*time.Second); err != nil { + if err := cache.SetContext(stdCtx, ctx.Cache, accessTokenCacheKey, at.AccessToken, time.Duration(expires)*time.Second); err != nil { return nil, nil } return at, nil } -// GetPreCode 获取预授权码 -func (ctx *Context) GetPreCode() (string, error) { - cat, err := ctx.GetComponentAccessToken() +// SetComponentAccessToken 通过component_verify_ticket 获取 ComponentAccessToken +func (ctx *Context) SetComponentAccessToken(stdCtx context.Context, verifyTicket string) (*ComponentAccessToken, error) { + return ctx.SetComponentAccessTokenContext(stdCtx, verifyTicket) +} + +// GetPreCodeContext 获取预授权码 +func (ctx *Context) GetPreCodeContext(stdCtx context.Context) (string, error) { + cat, err := ctx.GetComponentAccessTokenContext(stdCtx) if err != nil { return "", err } @@ -80,7 +92,7 @@ func (ctx *Context) GetPreCode() (string, error) { "component_appid": ctx.AppID, } uri := fmt.Sprintf(getPreCodeURL, cat) - body, err := util.PostJSON(uri, req) + body, err := util.PostJSONContext(stdCtx, uri, req) if err != nil { return "", err } @@ -95,24 +107,39 @@ func (ctx *Context) GetPreCode() (string, error) { return ret.PreCode, nil } -// GetComponentLoginPage 获取第三方公众号授权链接(扫码授权) -func (ctx *Context) GetComponentLoginPage(redirectURI string, authType int, bizAppID string) (string, error) { - code, err := ctx.GetPreCode() +// GetPreCode 获取预授权码 +func (ctx *Context) GetPreCode() (string, error) { + return ctx.GetPreCodeContext(context.Background()) +} + +// GetComponentLoginPageContext 获取第三方公众号授权链接(扫码授权) +func (ctx *Context) GetComponentLoginPageContext(stdCtx context.Context, redirectURI string, authType int, bizAppID string) (string, error) { + code, err := ctx.GetPreCodeContext(stdCtx) if err != nil { return "", err } return fmt.Sprintf(componentLoginURL, ctx.AppID, code, url.QueryEscape(redirectURI), authType, bizAppID), nil } -// GetBindComponentURL 获取第三方公众号授权链接(链接跳转,适用移动端) -func (ctx *Context) GetBindComponentURL(redirectURI string, authType int, bizAppID string) (string, error) { - code, err := ctx.GetPreCode() +// GetComponentLoginPage 获取第三方公众号授权链接(扫码授权) +func (ctx *Context) GetComponentLoginPage(redirectURI string, authType int, bizAppID string) (string, error) { + return ctx.GetComponentLoginPageContext(context.Background(), redirectURI, authType, bizAppID) +} + +// GetBindComponentURLContext 获取第三方公众号授权链接(链接跳转,适用移动端) +func (ctx *Context) GetBindComponentURLContext(stdCtx context.Context, redirectURI string, authType int, bizAppID string) (string, error) { + code, err := ctx.GetPreCodeContext(stdCtx) if err != nil { return "", err } return fmt.Sprintf(bindComponentURL, authType, ctx.AppID, code, url.QueryEscape(redirectURI), bizAppID), nil } +// GetBindComponentURL 获取第三方公众号授权链接(链接跳转,适用移动端) +func (ctx *Context) GetBindComponentURL(redirectURI string, authType int, bizAppID string) (string, error) { + return ctx.GetBindComponentURLContext(context.Background(), redirectURI, authType, bizAppID) +} + // ID 微信返回接口中各种类型字段 type ID struct { ID int `json:"id"` @@ -137,9 +164,9 @@ type AuthrAccessToken struct { RefreshToken string `json:"authorizer_refresh_token"` } -// QueryAuthCode 使用授权码换取公众号或小程序的接口调用凭据和授权信息 -func (ctx *Context) QueryAuthCode(authCode string) (*AuthBaseInfo, error) { - cat, err := ctx.GetComponentAccessToken() +// QueryAuthCodeContext 使用授权码换取公众号或小程序的接口调用凭据和授权信息 +func (ctx *Context) QueryAuthCodeContext(stdCtx context.Context, authCode string) (*AuthBaseInfo, error) { + cat, err := ctx.GetComponentAccessTokenContext(stdCtx) if err != nil { return nil, err } @@ -149,7 +176,7 @@ func (ctx *Context) QueryAuthCode(authCode string) (*AuthBaseInfo, error) { "authorization_code": authCode, } uri := fmt.Sprintf(queryAuthURL, cat) - body, err := util.PostJSON(uri, req) + body, err := util.PostJSONContext(stdCtx, uri, req) if err != nil { return nil, err } @@ -169,9 +196,14 @@ func (ctx *Context) QueryAuthCode(authCode string) (*AuthBaseInfo, error) { return ret.Info, nil } -// RefreshAuthrToken 获取(刷新)授权公众号或小程序的接口调用凭据(令牌) -func (ctx *Context) RefreshAuthrToken(appid, refreshToken string) (*AuthrAccessToken, error) { - cat, err := ctx.GetComponentAccessToken() +// QueryAuthCode 使用授权码换取公众号或小程序的接口调用凭据和授权信息 +func (ctx *Context) QueryAuthCode(authCode string) (*AuthBaseInfo, error) { + return ctx.QueryAuthCodeContext(context.Background(), authCode) +} + +// RefreshAuthrTokenContext 获取(刷新)授权公众号或小程序的接口调用凭据(令牌) +func (ctx *Context) RefreshAuthrTokenContext(stdCtx context.Context, appid, refreshToken string) (*AuthrAccessToken, error) { + cat, err := ctx.GetComponentAccessTokenContext(stdCtx) if err != nil { return nil, err } @@ -182,7 +214,7 @@ func (ctx *Context) RefreshAuthrToken(appid, refreshToken string) (*AuthrAccessT "authorizer_refresh_token": refreshToken, } uri := fmt.Sprintf(refreshTokenURL, cat) - body, err := util.PostJSON(uri, req) + body, err := util.PostJSONContext(stdCtx, uri, req) if err != nil { return nil, err } @@ -193,22 +225,32 @@ func (ctx *Context) RefreshAuthrToken(appid, refreshToken string) (*AuthrAccessT } authrTokenKey := "authorizer_access_token_" + appid - if err := ctx.Cache.Set(authrTokenKey, ret.AccessToken, time.Second*time.Duration(ret.ExpiresIn-30)); err != nil { + if err := cache.SetContext(stdCtx, ctx.Cache, authrTokenKey, ret.AccessToken, time.Second*time.Duration(ret.ExpiresIn-30)); err != nil { return nil, err } return ret, nil } -// GetAuthrAccessToken 获取授权方AccessToken -func (ctx *Context) GetAuthrAccessToken(appid string) (string, error) { +// RefreshAuthrToken 获取(刷新)授权公众号或小程序的接口调用凭据(令牌) +func (ctx *Context) RefreshAuthrToken(appid, refreshToken string) (*AuthrAccessToken, error) { + return ctx.RefreshAuthrTokenContext(context.Background(), appid, refreshToken) +} + +// GetAuthrAccessTokenContext 获取授权方AccessToken +func (ctx *Context) GetAuthrAccessTokenContext(stdCtx context.Context, appid string) (string, error) { authrTokenKey := "authorizer_access_token_" + appid - val := ctx.Cache.Get(authrTokenKey) + val := cache.GetContext(stdCtx, ctx.Cache, authrTokenKey) if val == nil { return "", fmt.Errorf("cannot get authorizer %s access token", appid) } return val.(string), nil } +// GetAuthrAccessToken 获取授权方AccessToken +func (ctx *Context) GetAuthrAccessToken(appid string) (string, error) { + return ctx.GetAuthrAccessTokenContext(context.Background(), appid) +} + // AuthorizerInfo 授权方详细信息 type AuthorizerInfo struct { NickName string `json:"nick_name"` @@ -258,9 +300,9 @@ type CategoriesInfo struct { Second string `wx:"second"` } -// GetAuthrInfo 获取授权方的帐号基本信息 -func (ctx *Context) GetAuthrInfo(appid string) (*AuthorizerInfo, *AuthBaseInfo, error) { - cat, err := ctx.GetComponentAccessToken() +// GetAuthrInfoContext 获取授权方的帐号基本信息 +func (ctx *Context) GetAuthrInfoContext(stdCtx context.Context, appid string) (*AuthorizerInfo, *AuthBaseInfo, error) { + cat, err := ctx.GetComponentAccessTokenContext(stdCtx) if err != nil { return nil, nil, err } @@ -271,7 +313,7 @@ func (ctx *Context) GetAuthrInfo(appid string) (*AuthorizerInfo, *AuthBaseInfo, } uri := fmt.Sprintf(getComponentInfoURL, cat) - body, err := util.PostJSON(uri, req) + body, err := util.PostJSONContext(stdCtx, uri, req) if err != nil { return nil, nil, err } @@ -286,3 +328,8 @@ func (ctx *Context) GetAuthrInfo(appid string) (*AuthorizerInfo, *AuthBaseInfo, return ret.AuthorizerInfo, ret.AuthorizationInfo, nil } + +// GetAuthrInfo 获取授权方的帐号基本信息 +func (ctx *Context) GetAuthrInfo(appid string) (*AuthorizerInfo, *AuthBaseInfo, error) { + return ctx.GetAuthrInfoContext(context.Background(), appid) +} diff --git a/util/http.go b/util/http.go index 38089aebe..fdd2f0abf 100644 --- a/util/http.go +++ b/util/http.go @@ -69,8 +69,8 @@ func HTTPPostContext(ctx context.Context, uri string, data []byte, header map[st return io.ReadAll(response.Body) } -// PostJSON post json 数据请求 -func PostJSON(uri string, obj interface{}) ([]byte, error) { +// PostJSONContext post json 数据请求 +func PostJSONContext(ctx context.Context, uri string, obj interface{}) ([]byte, error) { jsonBuf := new(bytes.Buffer) enc := json.NewEncoder(jsonBuf) enc.SetEscapeHTML(false) @@ -78,7 +78,12 @@ func PostJSON(uri string, obj interface{}) ([]byte, error) { if err != nil { return nil, err } - response, err := http.Post(uri, "application/json;charset=utf-8", jsonBuf) + req, err := http.NewRequestWithContext(ctx, "POST", uri, jsonBuf) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json;charset=utf-8") + response, err := http.DefaultClient.Do(req) if err != nil { return nil, err } @@ -90,6 +95,11 @@ func PostJSON(uri string, obj interface{}) ([]byte, error) { return io.ReadAll(response.Body) } +// PostJSON post json 数据请求 +func PostJSON(uri string, obj interface{}) ([]byte, error) { + return PostJSONContext(context.Background(), uri, obj) +} + // PostJSONWithRespContentType post json数据请求,且返回数据类型 func PostJSONWithRespContentType(uri string, obj interface{}) ([]byte, string, error) { jsonBuf := new(bytes.Buffer) From d6371c728933b127ad27228ade274d42c9a3b232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=89=9B=E5=BC=BA?= Date: Thu, 13 Apr 2023 14:23:46 +0800 Subject: [PATCH 19/27] =?UTF-8?q?chore:=20=E7=A7=BB=E9=99=A4=20panic?= =?UTF-8?q?=EF=BC=8C=E8=BD=AC=E7=A7=BB=20cache=20=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96=E5=88=B0=20Wechat=20=E7=BB=93=E6=9E=84=E4=BD=93?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E4=B8=8B=EF=BC=9B=E5=9C=A8=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E6=97=B6=E5=8F=AF=E4=BB=A5=E5=8F=AA=E8=AE=BE=E7=BD=AE=E4=B8=80?= =?UTF-8?q?=E6=AC=A1=20cache=20=E5=90=8C=E6=97=B6=E9=81=BF=E5=85=8D=20pani?= =?UTF-8?q?c=20=E5=87=BA=E7=8E=B0=20(#668)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openplatform/openplatform.go | 3 --- wechat.go | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openplatform/openplatform.go b/openplatform/openplatform.go index 80fb82e1b..5c509b927 100644 --- a/openplatform/openplatform.go +++ b/openplatform/openplatform.go @@ -18,9 +18,6 @@ type OpenPlatform struct { // NewOpenPlatform new openplatform func NewOpenPlatform(cfg *config.Config) *OpenPlatform { - if cfg.Cache == nil { - panic("cache 未设置") - } ctx := &context.Context{ Config: cfg, } diff --git a/wechat.go b/wechat.go index fc98400ac..e003dacb1 100644 --- a/wechat.go +++ b/wechat.go @@ -68,6 +68,9 @@ func (wc *Wechat) GetPay(cfg *payConfig.Config) *pay.Pay { // GetOpenPlatform 获取微信开放平台的实例 func (wc *Wechat) GetOpenPlatform(cfg *openConfig.Config) *openplatform.OpenPlatform { + if cfg.Cache == nil { + cfg.Cache = wc.cache + } return openplatform.NewOpenPlatform(cfg) } From cb0928a03cc108fef6b9995cd4e0969fa60e7674 Mon Sep 17 00:00:00 2001 From: misu Date: Tue, 18 Apr 2023 11:06:39 +0800 Subject: [PATCH 20/27] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E5=AE=9E=E4=BE=8B=EF=BC=8C=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E5=85=A8=E5=B1=80Cache=20(#671)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: masong --- wechat.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wechat.go b/wechat.go index e003dacb1..b2e280d54 100644 --- a/wechat.go +++ b/wechat.go @@ -76,5 +76,8 @@ func (wc *Wechat) GetOpenPlatform(cfg *openConfig.Config) *openplatform.OpenPlat // GetWork 获取企业微信的实例 func (wc *Wechat) GetWork(cfg *workConfig.Config) *work.Work { + if cfg.Cache == nil { + cfg.Cache = wc.cache + } return work.NewWork(cfg) } From 4094adc5b433d029ed9d62e3674a3eb5c4789bd3 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 18 Apr 2023 11:07:06 +0800 Subject: [PATCH 21/27] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E5=AE=A2=E6=9C=8D=E5=AD=97=E6=AE=B5=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=20(#670)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 企业微信微信客服字段同步 * fix ci lint --------- Co-authored-by: liuyuezhong --- work/kf/callback.go | 1 + work/kf/syncmsg.go | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/work/kf/callback.go b/work/kf/callback.go index 149137cce..56930c70f 100644 --- a/work/kf/callback.go +++ b/work/kf/callback.go @@ -57,6 +57,7 @@ type CallbackMessage struct { MsgType string `json:"msgtype"` // 消息的类型,此时固定为 event Event string `json:"event"` // 事件的类型,此时固定为 kf_msg_or_event Token string `json:"token"` // 调用拉取消息接口时,需要传此token,用于校验请求的合法性 + OpenKfID string `json:"open_kfid"` // 有新消息的客服帐号。可通过sync_msg接口指定open_kfid获取此客服帐号的消息 } // GetCallbackMessage 获取回调事件中的消息内容 diff --git a/work/kf/syncmsg.go b/work/kf/syncmsg.go index 20d06f5d8..dec4dffed 100644 --- a/work/kf/syncmsg.go +++ b/work/kf/syncmsg.go @@ -16,9 +16,11 @@ const ( // SyncMsgOptions 获取消息查询参数 type SyncMsgOptions struct { - Cursor string `json:"cursor"` // 上一次调用时返回的next_cursor,第一次拉取可以不填, 不多于64字节 - Token string `json:"token"` // 回调事件返回的token字段,10分钟内有效;可不填,如果不填接口有严格的频率限制, 不多于128字节 - Limit uint `json:"limit"` // 期望请求的数据量,默认值和最大值都为1000, 注意:可能会出现返回条数少于limit的情况,需结合返回的has_more字段判断是否继续请求。 + Cursor string `json:"cursor"` // 上一次调用时返回的next_cursor,第一次拉取可以不填, 不多于64字节 + Token string `json:"token"` // 回调事件返回的token字段,10分钟内有效;可不填,如果不填接口有严格的频率限制, 不多于128字节 + Limit uint `json:"limit"` // 期望请求的数据量,默认值和最大值都为1000, 注意:可能会出现返回条数少于limit的情况,需结合返回的has_more字段判断是否继续请求。 + VoiceFormat uint `json:"voice_format"` // 语音消息类型,0-Amr 1-Silk,默认0。可通过该参数控制返回的语音格式,开发者可按需选择自己程序支持的一种格式 + OpenKfID string `json:"open_kfid"` // 指定拉取某个客服帐号的消息,否则默认返回有权限的客服帐号的消息。当客服帐号较多,建议按open_kfid来拉取以获取更好的性能。 } // SyncMsgSchema 获取消息查询响应内容 From 8821a3856de84a70b4164e78d62b532ae15f8151 Mon Sep 17 00:00:00 2001 From: welllog <867804244@qq.com> Date: Tue, 18 Apr 2023 11:54:34 +0800 Subject: [PATCH 22/27] =?UTF-8?q?feat:=20=E8=8E=B7=E5=8F=96=E7=A8=B3?= =?UTF-8?q?=E5=AE=9A=E7=89=88=E6=8E=A5=E5=8F=A3=E8=B0=83=E7=94=A8=E5=87=AD?= =?UTF-8?q?=E6=8D=AE=20(#669)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 获取稳定版接口调用凭据 * Update default_access_token.go * fix: format code --------- Co-authored-by: ctr Co-authored-by: houseme --- credential/default_access_token.go | 84 +++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 6 deletions(-) diff --git a/credential/default_access_token.go b/credential/default_access_token.go index d58efe63e..25416e7c7 100644 --- a/credential/default_access_token.go +++ b/credential/default_access_token.go @@ -12,9 +12,11 @@ import ( ) const ( - // AccessTokenURL 获取access_token的接口 + // accessTokenURL 获取access_token的接口 accessTokenURL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s" - // AccessTokenURL 企业微信获取access_token的接口 + // stableAccessTokenURL 获取稳定版access_token的接口 + stableAccessTokenURL = "https://api.weixin.qq.com/cgi-bin/stable_token" + // workAccessTokenURL 企业微信获取access_token的接口 workAccessTokenURL = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s" // CacheKeyOfficialAccountPrefix 微信公众号cache key前缀 CacheKeyOfficialAccountPrefix = "gowechat_officialaccount_" @@ -79,20 +81,90 @@ func (ak *DefaultAccessToken) GetAccessTokenContext(ctx context.Context) (access // cache失效,从微信服务器获取 var resAccessToken ResAccessToken - resAccessToken, err = GetTokenFromServerContext(ctx, fmt.Sprintf(accessTokenURL, ak.appID, ak.appSecret)) - if err != nil { + if resAccessToken, err = GetTokenFromServerContext(ctx, fmt.Sprintf(accessTokenURL, ak.appID, ak.appSecret)); err != nil { return } - expires := resAccessToken.ExpiresIn - 1500 - err = ak.cache.Set(accessTokenCacheKey, resAccessToken.AccessToken, time.Duration(expires)*time.Second) + if err = ak.cache.Set(accessTokenCacheKey, resAccessToken.AccessToken, time.Duration(resAccessToken.ExpiresIn-1500)*time.Second); err != nil { + return + } + accessToken = resAccessToken.AccessToken + return +} + +// StableAccessToken 获取稳定版接口调用凭据(与getAccessToken获取的调用凭证完全隔离,互不影响) +// 不强制更新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 +} + +// NewStableAccessToken new StableAccessToken +func NewStableAccessToken(appID, appSecret, cacheKeyPrefix string, cache cache.Cache) AccessTokenContextHandle { + if cache == nil { + panic("cache is need") + } + return &StableAccessToken{ + appID: appID, + appSecret: appSecret, + cache: cache, + cacheKeyPrefix: cacheKeyPrefix, + } +} + +// GetAccessToken 获取access_token,先从cache中获取,没有则从服务端获取 +func (ak *StableAccessToken) GetAccessToken() (accessToken string, err error) { + return ak.GetAccessTokenContext(context.Background()) +} + +// GetAccessTokenContext 获取access_token,先从cache中获取,没有则从服务端获取 +func (ak *StableAccessToken) GetAccessTokenContext(ctx context.Context) (accessToken string, err error) { + // 先从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 + } + + // cache失效,从微信服务器获取 + var resAccessToken ResAccessToken + resAccessToken, err = ak.GetAccessTokenDirectly(ctx, false) if err != nil { return } + + expires := resAccessToken.ExpiresIn - 300 + _ = ak.cache.Set(accessTokenCacheKey, resAccessToken.AccessToken, time.Duration(expires)*time.Second) + accessToken = resAccessToken.AccessToken return } +// GetAccessTokenDirectly 从微信获取access_token +func (ak *StableAccessToken) GetAccessTokenDirectly(ctx context.Context, forceRefresh bool) (resAccessToken ResAccessToken, err error) { + b, err := util.PostJSONContext(ctx, stableAccessTokenURL, map[string]interface{}{ + "grant_type": "client_credential", + "appid": ak.appID, + "secret": ak.appSecret, + "force_refresh": forceRefresh, + }) + if err != nil { + return + } + + if err = json.Unmarshal(b, &resAccessToken); err != nil { + return + } + + if resAccessToken.ErrCode != 0 { + err = fmt.Errorf("get stable access_token error : errcode=%v , errormsg=%v", resAccessToken.ErrCode, resAccessToken.ErrMsg) + return + } + return +} + // WorkAccessToken 企业微信AccessToken 获取 type WorkAccessToken struct { CorpID string From 58621cd79d753e40ae15dd4bd951cf9838046c67 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 19 Apr 2023 16:21:04 +0800 Subject: [PATCH 23/27] fix CallbackMessage open_kfid and SyncMsg error (#672) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 企业微信微信客服字段同步 * fix ci lint * fix open_kfid xml Unmarshal error and 'syncMsg: field `open_kfid` unexpected empty string. invalid Request Parameter' * add struct CallbackMessage xml tag --------- Co-authored-by: liuyuezhong --- work/kf/callback.go | 12 ++++++------ work/kf/syncmsg.go | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/work/kf/callback.go b/work/kf/callback.go index 56930c70f..26ee1c583 100644 --- a/work/kf/callback.go +++ b/work/kf/callback.go @@ -52,12 +52,12 @@ type callbackOriginMessage struct { // CallbackMessage 微信客服回调消息 type CallbackMessage struct { - ToUserName string `json:"to_user_name"` // 微信客服组件ID - CreateTime int `json:"create_time"` // 消息创建时间,unix时间戳 - MsgType string `json:"msgtype"` // 消息的类型,此时固定为 event - Event string `json:"event"` // 事件的类型,此时固定为 kf_msg_or_event - Token string `json:"token"` // 调用拉取消息接口时,需要传此token,用于校验请求的合法性 - OpenKfID string `json:"open_kfid"` // 有新消息的客服帐号。可通过sync_msg接口指定open_kfid获取此客服帐号的消息 + ToUserName string `json:"to_user_name" xml:"ToUserName"` // 微信客服组件ID + CreateTime int `json:"create_time" xml:"CreateTime"` // 消息创建时间,unix时间戳 + MsgType string `json:"msgtype" xml:"MsgType"` // 消息的类型,此时固定为 event + Event string `json:"event" xml:"Event"` // 事件的类型,此时固定为 kf_msg_or_event + Token string `json:"token" xml:"Token"` // 调用拉取消息接口时,需要传此token,用于校验请求的合法性 + OpenKfID string `json:"open_kfid" xml:"OpenKfId"` // 有新消息的客服帐号。可通过sync_msg接口指定open_kfid获取此客服帐号的消息 } // GetCallbackMessage 获取回调事件中的消息内容 diff --git a/work/kf/syncmsg.go b/work/kf/syncmsg.go index dec4dffed..55ac07976 100644 --- a/work/kf/syncmsg.go +++ b/work/kf/syncmsg.go @@ -16,11 +16,11 @@ const ( // SyncMsgOptions 获取消息查询参数 type SyncMsgOptions struct { - Cursor string `json:"cursor"` // 上一次调用时返回的next_cursor,第一次拉取可以不填, 不多于64字节 - Token string `json:"token"` // 回调事件返回的token字段,10分钟内有效;可不填,如果不填接口有严格的频率限制, 不多于128字节 - Limit uint `json:"limit"` // 期望请求的数据量,默认值和最大值都为1000, 注意:可能会出现返回条数少于limit的情况,需结合返回的has_more字段判断是否继续请求。 - VoiceFormat uint `json:"voice_format"` // 语音消息类型,0-Amr 1-Silk,默认0。可通过该参数控制返回的语音格式,开发者可按需选择自己程序支持的一种格式 - OpenKfID string `json:"open_kfid"` // 指定拉取某个客服帐号的消息,否则默认返回有权限的客服帐号的消息。当客服帐号较多,建议按open_kfid来拉取以获取更好的性能。 + Cursor string `json:"cursor"` // 上一次调用时返回的next_cursor,第一次拉取可以不填, 不多于64字节 + Token string `json:"token"` // 回调事件返回的token字段,10分钟内有效;可不填,如果不填接口有严格的频率限制, 不多于128字节 + Limit uint `json:"limit"` // 期望请求的数据量,默认值和最大值都为1000, 注意:可能会出现返回条数少于limit的情况,需结合返回的has_more字段判断是否继续请求。 + VoiceFormat uint `json:"voice_format,omitempty"` // 语音消息类型,0-Amr 1-Silk,默认0。可通过该参数控制返回的语音格式,开发者可按需选择自己程序支持的一种格式 + OpenKfID string `json:"open_kfid,omitempty"` // 指定拉取某个客服帐号的消息,否则默认返回有权限的客服帐号的消息。当客服帐号较多,建议按open_kfid来拉取以获取更好的性能。 } // SyncMsgSchema 获取消息查询响应内容 From d92cd355330cf77ab54b518a156e3c8082975367 Mon Sep 17 00:00:00 2001 From: markwang <2951177317@qq.com> Date: Mon, 8 May 2023 11:56:48 +0800 Subject: [PATCH 24/27] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1-?= =?UTF-8?q?=E9=80=9A=E8=AE=AF=E5=BD=95=E7=AE=A1=E7=90=86-=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=E7=AE=A1=E7=90=86-=E8=8E=B7=E5=8F=96/=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0/=E5=88=A0=E9=99=A4=E6=A0=87=E7=AD=BE=E6=88=90?= =?UTF-8?q?=E5=91=98=E3=80=81=E8=8E=B7=E5=8F=96=E6=A0=87=E7=AD=BE=E5=88=97?= =?UTF-8?q?=E8=A1=A8=20(#674)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 企业微信-客户联系-统计管理 * 企业微信-客户联系-统计管理 * 企业微信-客户联系-统计管理 * debug * rollback * debug * debug * 获取用户信息 * token * json.Marshal错误输出 * debug * bugfix * 企业微信-通讯录管理相关接口 * 企业微信-通讯录管理 * 企业微信-通讯录管理 * 企业微信-通讯录管理 * 企业微信-[联系我]方式新增和查询 * 企业微信-[联系我]方式新增和获取 * 企业微信-[联系我]方式更新 * 企业微信-[联系我]方式列表、删除 * json.Marshal错误输出 * 已实现接口bug修改 * 历史接口bugfix * 历史接口bugfix * comment * 企业微信:客户联系-消息推送;素材管理-上传图片 * fix * 企业微信-获取群发记录列表 * 历史接口bugfix * 1.企业微信-客户联系-消息推送-入群欢迎语素材管理 2.企业微信-通讯录管理-成员管理-获取成员ID列表 * golangci-lint * gofmt * 方法访问命名 * 企业微信-批量获取客户详情入参优化 * 企业微信-通讯录管理-标签管理-创建/更新/删除标签 * 请求地址常量无需导出 * 企业微信保持代码风格统一,接口URL不导出 * 企业微信-通讯录管理-标签管理-获取/增加/删除标签成员、获取标签列表 --------- Co-authored-by: wang.yu Co-authored-by: markwang --- work/addresslist/tag.go | 150 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/work/addresslist/tag.go b/work/addresslist/tag.go index fe12acdf4..3e976e3e5 100644 --- a/work/addresslist/tag.go +++ b/work/addresslist/tag.go @@ -13,6 +13,14 @@ const ( updateTagURL = "https://qyapi.weixin.qq.com/cgi-bin/tag/update?access_token=%s" // deleteTagURL 删除标签 deleteTagURL = "https://qyapi.weixin.qq.com/cgi-bin/tag/delete?access_token=%s&tagid=%d" + // getTagURL 获取标签成员 + getTagURL = "https://qyapi.weixin.qq.com/cgi-bin/tag/get?access_token=%s&tagid=%d" + // addTagUsersURL 增加标签成员 + addTagUsersURL = "https://qyapi.weixin.qq.com/cgi-bin/tag/addtagusers?access_token=%s" + // delTagUsersURL 删除标签成员 + delTagUsersURL = "https://qyapi.weixin.qq.com/cgi-bin/tag/deltagusers?access_token=%s" + // listTagURL 获取标签列表 + listTagURL = "https://qyapi.weixin.qq.com/cgi-bin/tag/list?access_token=%s" ) type ( @@ -90,3 +98,145 @@ func (r *Client) DeleteTag(tagID int) error { } return util.DecodeWithCommonError(response, "DeleteTag") } + +type ( + // GetTagResponse 获取标签成员响应 + GetTagResponse struct { + util.CommonError + TagName string `json:"tagname"` + UserList []GetTagUserList `json:"userlist"` + PartyList []int `json:"partylist"` + } + // GetTagUserList 标签中包含的成员列表 + GetTagUserList struct { + UserID string `json:"userid"` + Name string `json:"name"` + } +) + +// GetTag 获取标签成员 +// @see https://developer.work.weixin.qq.com/document/path/90213 +func (r *Client) GetTag(tagID int) (*GetTagResponse, error) { + var ( + accessToken string + err error + ) + if accessToken, err = r.GetAccessToken(); err != nil { + return nil, err + } + var response []byte + if response, err = util.HTTPGet(fmt.Sprintf(getTagURL, accessToken, tagID)); err != nil { + return nil, err + } + result := &GetTagResponse{} + if err = util.DecodeWithError(response, result, "GetTag"); err != nil { + return nil, err + } + return result, nil +} + +type ( + // AddTagUsersRequest 增加标签成员请求 + AddTagUsersRequest struct { + TagID int `json:"tagid"` + UserList []string `json:"userlist"` + PartyList []int `json:"partylist"` + } + // AddTagUsersResponse 增加标签成员响应 + AddTagUsersResponse struct { + util.CommonError + InvalidList string `json:"invalidlist"` + InvalidParty []int `json:"invalidparty"` + } +) + +// AddTagUsers 增加标签成员 +// see https://developer.work.weixin.qq.com/document/path/90214 +func (r *Client) AddTagUsers(req *AddTagUsersRequest) (*AddTagUsersResponse, error) { + var ( + accessToken string + err error + ) + if accessToken, err = r.GetAccessToken(); err != nil { + return nil, err + } + var response []byte + if response, err = util.PostJSON(fmt.Sprintf(addTagUsersURL, accessToken), req); err != nil { + return nil, err + } + result := &AddTagUsersResponse{} + if err = util.DecodeWithError(response, result, "AddTagUsers"); err != nil { + return nil, err + } + return result, nil +} + +type ( + // DelTagUsersRequest 删除标签成员请求 + DelTagUsersRequest struct { + TagID int `json:"tagid"` + UserList []string `json:"userlist"` + PartyList []int `json:"partylist"` + } + // DelTagUsersResponse 删除标签成员响应 + DelTagUsersResponse struct { + util.CommonError + InvalidList string `json:"invalidlist"` + InvalidParty []int `json:"invalidparty"` + } +) + +// DelTagUsers 删除标签成员 +// see https://developer.work.weixin.qq.com/document/path/90215 +func (r *Client) DelTagUsers(req *DelTagUsersRequest) (*DelTagUsersResponse, error) { + var ( + accessToken string + err error + ) + if accessToken, err = r.GetAccessToken(); err != nil { + return nil, err + } + var response []byte + if response, err = util.PostJSON(fmt.Sprintf(delTagUsersURL, accessToken), req); err != nil { + return nil, err + } + result := &DelTagUsersResponse{} + if err = util.DecodeWithError(response, result, "DelTagUsers"); err != nil { + return nil, err + } + return result, nil +} + +type ( + // ListTagResponse 获取标签列表响应 + ListTagResponse struct { + util.CommonError + TagList []Tag `json:"taglist"` + } + // Tag 标签 + Tag struct { + TagID int `json:"tagid"` + TagName string `json:"tagname"` + } +) + +// ListTag 获取标签列表 +// @see https://developer.work.weixin.qq.com/document/path/90216 +func (r *Client) ListTag() (*ListTagResponse, error) { + var ( + accessToken string + err error + ) + if accessToken, err = r.GetAccessToken(); err != nil { + return nil, err + } + var response []byte + if response, err = util.HTTPGet(fmt.Sprintf(listTagURL, accessToken)); err != nil { + return nil, err + } + result := &ListTagResponse{} + if err = util.DecodeWithError(response, result, "ListTag"); err != nil { + return nil, err + } + return result, nil +} From b4f243ab13734c6f014c92ec8ca50449531a2d2e Mon Sep 17 00:00:00 2001 From: Sunny Date: Tue, 16 May 2023 19:20:40 +0800 Subject: [PATCH 25/27] =?UTF-8?q?Fix=EF=BC=9Aerr=20!=3D=20nil=20return=20n?= =?UTF-8?q?il=20(#680)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openplatform/miniprogram/component/component.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openplatform/miniprogram/component/component.go b/openplatform/miniprogram/component/component.go index 2ad70e0f5..8fff007f0 100644 --- a/openplatform/miniprogram/component/component.go +++ b/openplatform/miniprogram/component/component.go @@ -36,7 +36,7 @@ type RegisterMiniProgramParam struct { func (component *Component) RegisterMiniProgram(param *RegisterMiniProgramParam) error { componentAK, err := component.GetComponentAccessToken() if err != nil { - return nil + return err } url := fmt.Sprintf(fastregisterweappURL+"?action=create&component_access_token=%s", componentAK) data, err := util.PostJSON(url, param) @@ -58,7 +58,7 @@ type GetRegistrationStatusParam struct { func (component *Component) GetRegistrationStatus(param *GetRegistrationStatusParam) error { componentAK, err := component.GetComponentAccessToken() if err != nil { - return nil + return err } url := fmt.Sprintf(fastregisterweappURL+"?action=search&component_access_token=%s", componentAK) data, err := util.PostJSON(url, param) From 0a37184c2f5cda6ebdb67b425bd0eb3958215a37 Mon Sep 17 00:00:00 2001 From: just5325 <532508307@qq.com> Date: Tue, 16 May 2023 19:22:18 +0800 Subject: [PATCH 26/27] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E6=8E=A8=E9=80=81=E6=B6=88=E6=81=AF=E6=A8=A1=E5=9D=97=EF=BC=8C?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E9=83=A8=E5=88=86=E5=B8=B8=E7=94=A8=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E6=8E=A8=E9=80=81=EF=BC=9ASendText=20=E5=8F=91?= =?UTF-8?q?=E9=80=81=E6=96=87=E6=9C=AC=E6=B6=88=E6=81=AF=E3=80=81SendImage?= =?UTF-8?q?=20=E5=8F=91=E9=80=81=E5=9B=BE=E7=89=87=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E3=80=81SendVoice=20=E5=8F=91=E9=80=81=E8=AF=AD=E9=9F=B3?= =?UTF-8?q?=E6=B6=88=E6=81=AF=20=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E6=8E=A8=E9=80=81=E6=B6=88=E6=81=AF=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=EF=BC=9Ahttps://qyapi.weixin.qq.com/cgi-bin/appchat/s?= =?UTF-8?q?end=20(#678)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修正work.GetMessage()方法的注释信息 (cherry picked from commit 3dfaf9222910ae4ad977e2852908692d04118abc) * 新增应用推送消息模块,实现部分常用消息推送:SendText 发送文本消息、SendImage 发送图片消息、SendVoice 发送语音消息 企业微信应用推送消息接口:https://qyapi.weixin.qq.com/cgi-bin/appchat/send (cherry picked from commit 654ea158d6590a006a8c78ac3c38eafe361fe303) --- work/appchat/appchat.go | 115 ++++++++++++++++++++++++++++++++++++++++ work/appchat/client.go | 16 ++++++ work/work.go | 8 ++- 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 work/appchat/appchat.go create mode 100644 work/appchat/client.go diff --git a/work/appchat/appchat.go b/work/appchat/appchat.go new file mode 100644 index 000000000..65d565082 --- /dev/null +++ b/work/appchat/appchat.go @@ -0,0 +1,115 @@ +package appchat + +import ( + "encoding/json" + "fmt" + + "github.com/silenceper/wechat/v2/util" +) + +const ( + // 应用推送消息接口地址 + sendURL = "https://qyapi.weixin.qq.com/cgi-bin/appchat/send?access_token=%s" +) + +type ( + // SendRequestCommon 发送应用推送消息请求公共参数 + SendRequestCommon struct { + // 群聊id + ChatID string `json:"chatid"` + // 消息类型 + MsgType string `json:"msgtype"` + // 表示是否是保密消息,0表示否,1表示是,默认0 + Safe int `json:"safe"` + } + + // SendResponse 发送应用消息响应参数 + SendResponse struct { + util.CommonError + } + + // SendTextRequest 发送文本消息的请求 + SendTextRequest struct { + *SendRequestCommon + Text TextField `json:"text"` + } + // TextField 文本消息参数 + TextField struct { + // 消息内容,最长不超过2048个字节 + Content string `json:"content"` + } + + // SendImageRequest 发送图片消息的请求 + SendImageRequest struct { + *SendRequestCommon + Image ImageField `json:"image"` + } + // ImageField 图片消息参数 + ImageField struct { + // 图片媒体文件id,可以调用上传临时素材接口获取 + MediaID string `json:"media_id"` + } + + // SendVoiceRequest 发送语音消息的请求 + SendVoiceRequest struct { + *SendRequestCommon + Voice VoiceField `json:"voice"` + } + // VoiceField 语音消息参数 + VoiceField struct { + // 语音文件id,可以调用上传临时素材接口获取 + MediaID string `json:"media_id"` + } +) + +// Send 发送应用消息 +// @desc 实现企业微信发送应用消息接口:https://developer.work.weixin.qq.com/document/path/90248 +func (r *Client) Send(apiName string, request interface{}) (*SendResponse, error) { + // 获取accessToken + accessToken, err := r.GetAccessToken() + if err != nil { + return nil, err + } + // 请求参数转 JSON 格式 + jsonData, err := json.Marshal(request) + if err != nil { + return nil, err + } + // 发起http请求 + response, err := util.HTTPPost(fmt.Sprintf(sendURL, accessToken), string(jsonData)) + if err != nil { + return nil, err + } + // 按照结构体解析返回值 + result := &SendResponse{} + if err = util.DecodeWithError(response, result, apiName); err != nil { + return nil, err + } + // 返回数据 + return result, nil +} + +// SendText 发送文本消息 +func (r *Client) SendText(request SendTextRequest) (*SendResponse, error) { + // 发送文本消息MsgType参数固定为:text + request.MsgType = "text" + return r.Send("MessageSendText", request) +} + +// SendImage 发送图片消息 +func (r *Client) SendImage(request SendImageRequest) (*SendResponse, error) { + // 发送图片消息MsgType参数固定为:image + request.MsgType = "image" + return r.Send("MessageSendImage", request) +} + +// SendVoice 发送语音消息 +func (r *Client) SendVoice(request SendVoiceRequest) (*SendResponse, error) { + // 发送语音消息MsgType参数固定为:voice + request.MsgType = "voice" + return r.Send("MessageSendVoice", request) +} + +// 以上实现了部分常用消息推送:SendText 发送文本消息、SendImage 发送图片消息、SendVoice 发送语音消息, +// 如需扩展其他消息类型,建议按照以上格式,扩展对应消息类型的参数即可 +// 也可以直接使用Send方法,按照企业微信消息推送的接口文档传对应消息类型的参数来使用 diff --git a/work/appchat/client.go b/work/appchat/client.go new file mode 100644 index 000000000..246ad35fc --- /dev/null +++ b/work/appchat/client.go @@ -0,0 +1,16 @@ +// Package appchat 应用发送消息到群聊会话,企业微信接口:https://developer.work.weixin.qq.com/document/path/90248 +package appchat + +import ( + "github.com/silenceper/wechat/v2/work/context" +) + +// Client 接口实例 +type Client struct { + *context.Context +} + +// NewClient 初始化实例 +func NewClient(ctx *context.Context) *Client { + return &Client{ctx} +} diff --git a/work/work.go b/work/work.go index 869809bd3..d394658bf 100644 --- a/work/work.go +++ b/work/work.go @@ -3,6 +3,7 @@ package work import ( "github.com/silenceper/wechat/v2/credential" "github.com/silenceper/wechat/v2/work/addresslist" + "github.com/silenceper/wechat/v2/work/appchat" "github.com/silenceper/wechat/v2/work/config" "github.com/silenceper/wechat/v2/work/context" "github.com/silenceper/wechat/v2/work/externalcontact" @@ -69,7 +70,12 @@ func (wk *Work) GetRobot() *robot.Client { return robot.NewClient(wk.ctx) } -// GetMessage get robot +// GetMessage 获取发送应用消息接口实例 func (wk *Work) GetMessage() *message.Client { return message.NewClient(wk.ctx) } + +// GetAppChat 获取应用发送消息到群聊会话接口实例 +func (wk *Work) GetAppChat() *appchat.Client { + return appchat.NewClient(wk.ctx) +} From 9df943df69f0d33a5f1ecaf5e8fd1ecaa3288aa1 Mon Sep 17 00:00:00 2001 From: markwang <2951177317@qq.com> Date: Tue, 16 May 2023 19:22:56 +0800 Subject: [PATCH 27/27] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1useri?= =?UTF-8?q?d=E4=B8=8Eopenid=E4=BA=92=E6=8D=A2=20(#676)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 企业微信-客户联系-统计管理 * 企业微信-客户联系-统计管理 * 企业微信-客户联系-统计管理 * debug * rollback * debug * debug * 获取用户信息 * token * json.Marshal错误输出 * debug * bugfix * 企业微信-通讯录管理相关接口 * 企业微信-通讯录管理 * 企业微信-通讯录管理 * 企业微信-通讯录管理 * 企业微信-[联系我]方式新增和查询 * 企业微信-[联系我]方式新增和获取 * 企业微信-[联系我]方式更新 * 企业微信-[联系我]方式列表、删除 * json.Marshal错误输出 * 已实现接口bug修改 * 历史接口bugfix * 历史接口bugfix * comment * 企业微信:客户联系-消息推送;素材管理-上传图片 * fix * 企业微信-获取群发记录列表 * 历史接口bugfix * 1.企业微信-客户联系-消息推送-入群欢迎语素材管理 2.企业微信-通讯录管理-成员管理-获取成员ID列表 * golangci-lint * gofmt * 方法访问命名 * 企业微信-批量获取客户详情入参优化 * 企业微信-通讯录管理-标签管理-创建/更新/删除标签 * 请求地址常量无需导出 * 企业微信保持代码风格统一,接口URL不导出 * 企业微信-通讯录管理-标签管理-获取/增加/删除标签成员、获取标签列表 * feat:企业微信userid与openid互换 --------- Co-authored-by: wang.yu Co-authored-by: markwang --- work/addresslist/user.go | 76 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/work/addresslist/user.go b/work/addresslist/user.go index 4b6a88c4f..d5b2a63c6 100644 --- a/work/addresslist/user.go +++ b/work/addresslist/user.go @@ -13,6 +13,10 @@ const ( userGetURL = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s" // userListIDURL 获取成员ID列表 userListIDURL = "https://qyapi.weixin.qq.com/cgi-bin/user/list_id?access_token=%s" + // convertToOpenIDURL userID转openID + convertToOpenIDURL = "https://qyapi.weixin.qq.com/cgi-bin/user/convert_to_openid?access_token=%s" + // convertToUserIDURL openID转userID + convertToUserIDURL = "https://qyapi.weixin.qq.com/cgi-bin/user/convert_to_userid?access_token=%s" ) type ( @@ -175,3 +179,75 @@ func (r *Client) UserListID(req *UserListIDRequest) (*UserListIDResponse, error) } return result, nil } + +type ( + // convertToOpenIDRequest userID转openID请求 + convertToOpenIDRequest struct { + UserID string `json:"userid"` + } + + // convertToOpenIDResponse userID转openID响应 + convertToOpenIDResponse struct { + util.CommonError + OpenID string `json:"openid"` + } +) + +// ConvertToOpenID userID转openID +// see https://developer.work.weixin.qq.com/document/path/90202 +func (r *Client) ConvertToOpenID(userID string) (string, 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(convertToOpenIDURL, accessToken), &convertToOpenIDRequest{ + UserID: userID, + }); err != nil { + return "", err + } + result := &convertToOpenIDResponse{} + if err = util.DecodeWithError(response, result, "ConvertToOpenID"); err != nil { + return "", err + } + return result.OpenID, nil +} + +type ( + // convertToUserIDRequest openID转userID请求 + convertToUserIDRequest struct { + OpenID string `json:"openid"` + } + + // convertToUserIDResponse openID转userID响应 + convertToUserIDResponse struct { + util.CommonError + UserID string `json:"userid"` + } +) + +// ConvertToUserID openID转userID +// see https://developer.work.weixin.qq.com/document/path/90202 +func (r *Client) ConvertToUserID(openID string) (string, 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(convertToUserIDURL, accessToken), &convertToUserIDRequest{ + OpenID: openID, + }); err != nil { + return "", err + } + result := &convertToUserIDResponse{} + if err = util.DecodeWithError(response, result, "ConvertToUserID"); err != nil { + return "", err + } + return result.UserID, nil +}