1
0
mirror of https://github.com/silenceper/wechat.git synced 2026-02-23 13:42:25 +08:00

Compare commits

..

10 Commits

Author SHA1 Message Date
houseme
f26a66ec9c Merge 356fe3df44 into 038037b89d 2023-10-13 17:11:50 +00:00
houseme
356fe3df44 fix 2023-10-14 01:11:35 +08:00
houseme
9e54a6a27b fix 2023-10-14 01:05:01 +08:00
houseme
ef4e3a0a3e test 2023-10-14 00:59:32 +08:00
houseme
12dfb7051a test 2023-10-14 00:54:18 +08:00
houseme
d214617446 feat: improve code for subscribe 2023-10-12 13:29:01 +08:00
houseme
b3e2d624d9 feat: add v1.21 and feature branch 2023-10-11 23:54:40 +08:00
houseme
7371d1649f feat: improve subscribe msg 2023-10-11 23:51:24 +08:00
Ralph Maas
b1f31feff6 feat(miniapp): 小程序订阅消息 (#429)
1. 用户订阅消息服务端回调处理
2. 用户订阅消息订阅通知事件推送

Co-authored-by: houseme <qzg40737@163.com>
2023-10-11 09:06:49 -05:00
houseme
b39be7fca2 fix 2023-10-11 22:04:23 +08:00
11 changed files with 86 additions and 694 deletions

View File

@@ -66,11 +66,9 @@ func (ak *DefaultAccessToken) GetAccessToken() (accessToken string, err error) {
func (ak *DefaultAccessToken) GetAccessTokenContext(ctx context.Context) (accessToken string, err error) { func (ak *DefaultAccessToken) GetAccessTokenContext(ctx context.Context) (accessToken string, err error) {
// 先从cache中取 // 先从cache中取
accessTokenCacheKey := fmt.Sprintf("%s_access_token_%s", ak.cacheKeyPrefix, ak.appID) accessTokenCacheKey := fmt.Sprintf("%s_access_token_%s", ak.cacheKeyPrefix, ak.appID)
val := ak.cache.Get(accessTokenCacheKey)
if val := ak.cache.Get(accessTokenCacheKey); val != nil { if accessToken = val.(string); accessToken != "" {
if accessToken = val.(string); accessToken != "" { return
return
}
} }
// 加上lock是为了防止在并发获取token时cache刚好失效导致从微信服务器上获取到不同token // 加上lock是为了防止在并发获取token时cache刚好失效导致从微信服务器上获取到不同token
@@ -78,10 +76,9 @@ func (ak *DefaultAccessToken) GetAccessTokenContext(ctx context.Context) (access
defer ak.accessTokenLock.Unlock() defer ak.accessTokenLock.Unlock()
// 双检,防止重复从微信服务器获取 // 双检,防止重复从微信服务器获取
if val := ak.cache.Get(accessTokenCacheKey); val != nil { val = ak.cache.Get(accessTokenCacheKey)
if accessToken = val.(string); accessToken != "" { if accessToken = val.(string); accessToken != "" {
return return
}
} }
// cache失效从微信服务器获取 // cache失效从微信服务器获取

View File

@@ -90,12 +90,10 @@ host: https://qyapi.weixin.qq.com/
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 | 贡献者 | | 名称 | 请求方式 | URL | 是否已实现 | 使用方法 | 贡献者 |
|:---------:|------|:----------------------------------------| ---------- | ------------------------------- |----------| |:---------:|------|:----------------------------------------| ---------- | ------------------------------- |----------|
| 获取子部门ID列表 | GET | /cgi-bin/department/simplelist | YES | (r *Client) DepartmentSimpleList| MARKWANG | | 获取子部门ID列表 | GET | /cgi-bin/department/simplelist | YES | (r *Client) DepartmentSimpleList| MARKWANG |
| 获取部门列表 | GET | /cgi-bin/department/list | YES | (r *Client) DepartmentList| just5325, ourines |
| 获取部门成员 | GET | /cgi-bin/user/simplelist | YES | (r *Client) UserSimpleList | MARKWANG | | 获取部门成员 | GET | /cgi-bin/user/simplelist | YES | (r *Client) UserSimpleList | MARKWANG |
| 获取成员ID列表 | Post | /cgi-bin/user/list_id | YES | (r *Client) UserListId | MARKWANG | | 获取成员ID列表 | Post | /cgi-bin/user/list_id | YES | (r *Client) UserListId | MARKWANG |
## 素材管理 ## 素材管理
[官方文档](https://developer.work.weixin.qq.com/document/path/91054) [官方文档](https://developer.work.weixin.qq.com/document/path/91054)

View File

@@ -2,12 +2,12 @@ package openapi
import "github.com/silenceper/wechat/v2/util" import "github.com/silenceper/wechat/v2/util"
// GetAPIQuotaParams 查询 API 调用额度参数 // GetAPIQuotaParams 查询API调用额度参数
type GetAPIQuotaParams struct { type GetAPIQuotaParams struct {
CgiPath string `json:"cgi_path"` // api 的请求地址,例如"/cgi-bin/message/custom/send";不要前缀“https://api.weixin.qq.com” ,也不要漏了"/",否则都会 76003 的报错 CgiPath string `json:"cgi_path"` // api的请求地址例如"/cgi-bin/message/custom/send";不要前缀“https://api.weixin.qq.com” ,也不要漏了"/",否则都会76003的报错
} }
// APIQuota API 调用额度 // APIQuota API调用额度
type APIQuota struct { type APIQuota struct {
util.CommonError util.CommonError
Quota struct { Quota struct {
@@ -17,20 +17,20 @@ type APIQuota struct {
} `json:"quota"` // 详情 } `json:"quota"` // 详情
} }
// GetRidInfoParams 查询 rid 信息参数 // GetRidInfoParams 查询rid信息参数
type GetRidInfoParams struct { type GetRidInfoParams struct {
Rid string `json:"rid"` // 调用接口报错返回的 rid Rid string `json:"rid"` // 调用接口报错返回的rid
} }
// RidInfo rid 信息 // RidInfo rid信息
type RidInfo struct { type RidInfo struct {
util.CommonError util.CommonError
Request struct { Request struct {
InvokeTime int64 `json:"invoke_time"` // 发起请求的时间戳 InvokeTime int64 `json:"invoke_time"` // 发起请求的时间戳
CostInMs int64 `json:"cost_in_ms"` // 请求毫秒级耗时 CostInMs int64 `json:"cost_in_ms"` // 请求毫秒级耗时
RequestURL string `json:"request_url"` // 请求的 URL 参数 RequestURL string `json:"request_url"` // 请求的URL参数
RequestBody string `json:"request_body"` // post 请求的请求参数 RequestBody string `json:"request_body"` // post请求的请求参数
ResponseBody string `json:"response_body"` // 接口请求返回参数 ResponseBody string `json:"response_body"` // 接口请求返回参数
ClientIP string `json:"client_ip"` // 接口请求的客户端 ip ClientIP string `json:"client_ip"` // 接口请求的客户端ip
} `json:"request"` // 该 rid 对应的请求详情 } `json:"request"` // 该rid对应的请求详情
} }

View File

@@ -9,8 +9,6 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/tidwall/gjson"
"github.com/silenceper/wechat/v2/miniprogram/context" "github.com/silenceper/wechat/v2/miniprogram/context"
"github.com/silenceper/wechat/v2/miniprogram/security" "github.com/silenceper/wechat/v2/miniprogram/security"
"github.com/silenceper/wechat/v2/util" "github.com/silenceper/wechat/v2/util"
@@ -205,96 +203,32 @@ func (receiver *PushReceiver) getEvent(dataType string, eventType EventType, dec
return &pushData, err return &pushData, err
case EventSubscribePopup: case EventSubscribePopup:
// 用户操作订阅通知弹窗事件推送 // 用户操作订阅通知弹窗事件推送
return receiver.unmarshalSubscribePopup(dataType, decryptMsg) var pushData PushDataSubscribePopup
err := receiver.unmarshal(dataType, decryptMsg, &pushData)
return &pushData, err
case EventSubscribeMsgChange: case EventSubscribeMsgChange:
// 用户管理订阅通知事件推送 // 用户管理订阅通知事件推送
return receiver.unmarshalSubscribeMsgChange(dataType, decryptMsg) var pushData PushDataSubscribeMsgChange
err := receiver.unmarshal(dataType, decryptMsg, &pushData)
return &pushData, err
case EventSubscribeMsgSent: case EventSubscribeMsgSent:
// 用户发送订阅通知事件推送 // 用户发送订阅通知事件推送
return receiver.unmarshalSubscribeMsgSent(dataType, decryptMsg) var pushData PushDataSubscribeMsgSent
err := receiver.unmarshal(dataType, decryptMsg, &pushData)
return &pushData, err
} }
// 暂不支持其他事件类型,直接返回解密后的数据,由调用方处理 // 暂不支持其他事件类型,直接返回解密后的数据,由调用方处理
return decryptMsg, nil return decryptMsg, nil
} }
// unmarshal 解析推送的数据 // unmarshal 解析推送的数据
func (receiver *PushReceiver) unmarshal(dataType string, decryptMsg []byte, pushData interface{}) error { func (receiver *PushReceiver) unmarshal(dateType string, decryptMsg []byte, pushData interface{}) error {
if dataType == DataTypeXML { if dateType == DataTypeXML {
return xml.Unmarshal(decryptMsg, pushData) return xml.Unmarshal(decryptMsg, pushData)
} }
return json.Unmarshal(decryptMsg, pushData) return json.Unmarshal(decryptMsg, pushData)
} }
// unmarshalSubscribePopup
func (receiver *PushReceiver) unmarshalSubscribePopup(dataType string, decryptMsg []byte) (PushData, error) {
var pushData PushDataSubscribePopup
err := receiver.unmarshal(dataType, decryptMsg, &pushData)
if err == nil {
listData := gjson.Get(string(decryptMsg), "List")
if listData.IsObject() {
listItem := SubscribeMsgPopupEventList{}
if parseErr := json.Unmarshal([]byte(listData.Raw), &listItem); parseErr != nil {
return &pushData, parseErr
}
pushData.SetSubscribeMsgPopupEvents([]SubscribeMsgPopupEventList{listItem})
} else if listData.IsArray() {
listItems := make([]SubscribeMsgPopupEventList, 0)
if parseErr := json.Unmarshal([]byte(listData.Raw), &listItems); parseErr != nil {
return &pushData, parseErr
}
pushData.SetSubscribeMsgPopupEvents(listItems)
}
}
return &pushData, err
}
// unmarshalSubscribeMsgChange 解析用户管理订阅通知事件推送
func (receiver *PushReceiver) unmarshalSubscribeMsgChange(dataType string, decryptMsg []byte) (PushData, error) {
var pushData PushDataSubscribeMsgChange
err := receiver.unmarshal(dataType, decryptMsg, &pushData)
if err == nil {
listData := gjson.Get(string(decryptMsg), "List")
if listData.IsObject() {
listItem := SubscribeMsgChangeList{}
if parseErr := json.Unmarshal([]byte(listData.Raw), &listItem); parseErr != nil {
return &pushData, parseErr
}
pushData.SetSubscribeMsgChangeEvents([]SubscribeMsgChangeList{listItem})
} else if listData.IsArray() {
listItems := make([]SubscribeMsgChangeList, 0)
if parseErr := json.Unmarshal([]byte(listData.Raw), &listItems); parseErr != nil {
return &pushData, parseErr
}
pushData.SetSubscribeMsgChangeEvents(listItems)
}
}
return &pushData, err
}
// unmarshalSubscribeMsgSent 解析用户发送订阅通知事件推送
func (receiver *PushReceiver) unmarshalSubscribeMsgSent(dataType string, decryptMsg []byte) (PushData, error) {
var pushData PushDataSubscribeMsgSent
err := receiver.unmarshal(dataType, decryptMsg, &pushData)
if err == nil {
listData := gjson.Get(string(decryptMsg), "List")
if listData.IsObject() {
listItem := SubscribeMsgSentList{}
if parseErr := json.Unmarshal([]byte(listData.Raw), &listItem); parseErr != nil {
return &pushData, parseErr
}
pushData.SetSubscribeMsgSentEvents([]SubscribeMsgSentList{listItem})
} else if listData.IsArray() {
listItems := make([]SubscribeMsgSentList, 0)
if parseErr := json.Unmarshal([]byte(listData.Raw), &listItems); parseErr != nil {
return &pushData, parseErr
}
pushData.SetSubscribeMsgSentEvents(listItems)
}
}
return &pushData, err
}
// DataReceived 接收到的数据 // DataReceived 接收到的数据
type DataReceived struct { type DataReceived struct {
Encrypt string `json:"Encrypt" xml:"Encrypt"` // 加密的消息体 Encrypt string `json:"Encrypt" xml:"Encrypt"` // 加密的消息体
@@ -471,13 +405,12 @@ type CoinInfo struct {
// PushDataSubscribePopup 用户操作订阅通知弹窗事件推送 // PushDataSubscribePopup 用户操作订阅通知弹窗事件推送
type PushDataSubscribePopup struct { type PushDataSubscribePopup struct {
CommonPushData CommonPushData
subscribeMsgPopupEventList []SubscribeMsgPopupEventList `json:"-"` List []SubscribeMsgPopupEventList `xml:"SubscribeMsgPopupEvent>List" json:"List"`
SubscribeMsgPopupEvent SubscribeMsgPopupEvent `xml:"SubscribeMsgPopupEvent"`
} }
// SubscribeMsgPopupEvent 用户操作订阅通知弹窗消息回调 // SubscribeMsgPopupEvent 用户操作订阅通知弹窗消息回调
type SubscribeMsgPopupEvent struct { type SubscribeMsgPopupEvent struct {
List []SubscribeMsgPopupEventList `xml:"List"` List []SubscribeMsgPopupEventList `xml:"List" json:"List"`
} }
// SubscribeMsgPopupEventList 订阅消息事件列表 // SubscribeMsgPopupEventList 订阅消息事件列表
@@ -487,28 +420,10 @@ type SubscribeMsgPopupEventList struct {
PopupScene string `xml:"PopupScene" json:"PopupScene"` PopupScene string `xml:"PopupScene" json:"PopupScene"`
} }
// SetSubscribeMsgPopupEvents 设置订阅消息事件
func (s *PushDataSubscribePopup) SetSubscribeMsgPopupEvents(list []SubscribeMsgPopupEventList) {
s.subscribeMsgPopupEventList = list
}
// GetSubscribeMsgPopupEvents 获取订阅消息事件数据
func (s *PushDataSubscribePopup) GetSubscribeMsgPopupEvents() []SubscribeMsgPopupEventList {
if s.subscribeMsgPopupEventList != nil {
return s.subscribeMsgPopupEventList
}
if s.SubscribeMsgPopupEvent.List == nil || len(s.SubscribeMsgPopupEvent.List) < 1 {
return nil
}
return s.SubscribeMsgPopupEvent.List
}
// PushDataSubscribeMsgChange 用户管理订阅通知事件推送 // PushDataSubscribeMsgChange 用户管理订阅通知事件推送
type PushDataSubscribeMsgChange struct { type PushDataSubscribeMsgChange struct {
CommonPushData CommonPushData
SubscribeMsgChangeEvent SubscribeMsgChangeEvent `xml:"SubscribeMsgChangeEvent"` List []SubscribeMsgChangeList `xml:"SubscribeMsgChangeEvent>List" json:"List"`
subscribeMsgChangeList []SubscribeMsgChangeList `json:"-"`
} }
// SubscribeMsgChangeEvent 用户管理订阅通知回调 // SubscribeMsgChangeEvent 用户管理订阅通知回调
@@ -522,58 +437,21 @@ type SubscribeMsgChangeList struct {
SubscribeStatusString string `xml:"SubscribeStatusString" json:"SubscribeStatusString"` SubscribeStatusString string `xml:"SubscribeStatusString" json:"SubscribeStatusString"`
} }
// SetSubscribeMsgChangeEvents 设置订阅消息事件
func (s *PushDataSubscribeMsgChange) SetSubscribeMsgChangeEvents(list []SubscribeMsgChangeList) {
s.subscribeMsgChangeList = list
}
// GetSubscribeMsgChangeEvents 获取订阅消息事件数据
func (s *PushDataSubscribeMsgChange) GetSubscribeMsgChangeEvents() []SubscribeMsgChangeList {
if s.subscribeMsgChangeList != nil {
return s.subscribeMsgChangeList
}
if s.SubscribeMsgChangeEvent.List == nil || len(s.SubscribeMsgChangeEvent.List) < 1 {
return nil
}
return s.SubscribeMsgChangeEvent.List
}
// PushDataSubscribeMsgSent 用户发送订阅通知事件推送 // PushDataSubscribeMsgSent 用户发送订阅通知事件推送
type PushDataSubscribeMsgSent struct { type PushDataSubscribeMsgSent struct {
CommonPushData CommonPushData
SubscribeMsgSentEvent SubscribeMsgSentEvent `xml:"SubscribeMsgSentEvent"` List []SubscribeMsgSentEventList `xml:"SubscribeMsgSentEvent>List" json:"List"`
subscribeMsgSentEventList []SubscribeMsgSentList `json:"-"`
} }
// SubscribeMsgSentEvent 用户发送订阅通知回调 // SubscribeMsgSentEvent 用户发送订阅通知回调
type SubscribeMsgSentEvent struct { type SubscribeMsgSentEvent struct {
List []SubscribeMsgSentList `xml:"List" json:"List"` List []SubscribeMsgSentEventList `xml:"List" json:"List"`
} }
// SubscribeMsgSentList 订阅消息事件列表 // SubscribeMsgSentEventList 订阅消息事件列表
type SubscribeMsgSentList struct { type SubscribeMsgSentEventList struct {
TemplateID string `xml:"TemplateId" json:"TemplateId"` TemplateID string `xml:"TemplateId" json:"TemplateId"`
MsgID string `xml:"MsgID" json:"MsgID"` MsgID string `xml:"MsgID" json:"MsgID"`
ErrorCode int `xml:"ErrorCode" json:"ErrorCode"` ErrorCode int `xml:"ErrorCode" json:"ErrorCode"`
ErrorStatus string `xml:"ErrorStatus" json:"ErrorStatus"` ErrorStatus string `xml:"ErrorStatus" json:"ErrorStatus"`
} }
// SetSubscribeMsgSentEvents 设置订阅消息事件
func (s *PushDataSubscribeMsgSent) SetSubscribeMsgSentEvents(list []SubscribeMsgSentList) {
s.subscribeMsgSentEventList = list
}
// GetSubscribeMsgSentEvents 获取订阅消息事件数据
func (s *PushDataSubscribeMsgSent) GetSubscribeMsgSentEvents() []SubscribeMsgSentList {
if s.subscribeMsgSentEventList != nil {
return s.subscribeMsgSentEventList
}
if s.SubscribeMsgSentEvent.List == nil || len(s.SubscribeMsgSentEvent.List) < 1 {
return nil
}
return s.SubscribeMsgSentEvent.List
}

View File

@@ -27,15 +27,15 @@ const (
MsgTypeVideo MsgType = "video" MsgTypeVideo MsgType = "video"
// MsgTypeMiniprogrampage 表示小程序卡片消息 // MsgTypeMiniprogrampage 表示小程序卡片消息
MsgTypeMiniprogrampage MsgType = "miniprogrampage" MsgTypeMiniprogrampage MsgType = "miniprogrampage"
// MsgTypeShortVideo 表示短视频消息 [限接收] // MsgTypeShortVideo 表示短视频消息[限接收]
MsgTypeShortVideo MsgType = "shortvideo" MsgTypeShortVideo MsgType = "shortvideo"
// MsgTypeLocation 表示坐标消息 [限接收] // MsgTypeLocation 表示坐标消息[限接收]
MsgTypeLocation MsgType = "location" MsgTypeLocation MsgType = "location"
// MsgTypeLink 表示链接消息 [限接收] // MsgTypeLink 表示链接消息[限接收]
MsgTypeLink MsgType = "link" MsgTypeLink MsgType = "link"
// MsgTypeMusic 表示音乐消息 [限回复] // MsgTypeMusic 表示音乐消息[限回复]
MsgTypeMusic MsgType = "music" MsgTypeMusic MsgType = "music"
// MsgTypeNews 表示图文消息 [限回复] // MsgTypeNews 表示图文消息[限回复]
MsgTypeNews MsgType = "news" MsgTypeNews MsgType = "news"
// MsgTypeTransfer 表示消息消息转发到客服 // MsgTypeTransfer 表示消息消息转发到客服
MsgTypeTransfer MsgType = "transfer_customer_service" MsgTypeTransfer MsgType = "transfer_customer_service"
@@ -91,7 +91,7 @@ const (
const ( const (
// 微信开放平台需要用到 // 微信开放平台需要用到
// InfoTypeVerifyTicket 返回 ticket // InfoTypeVerifyTicket 返回ticket
InfoTypeVerifyTicket InfoType = "component_verify_ticket" InfoTypeVerifyTicket InfoType = "component_verify_ticket"
// InfoTypeAuthorized 授权 // InfoTypeAuthorized 授权
InfoTypeAuthorized InfoType = "authorized" InfoTypeAuthorized InfoType = "authorized"
@@ -108,8 +108,8 @@ type MixMessage struct {
CommonToken CommonToken
// 基本消息 // 基本消息
MsgID int64 `xml:"MsgId"` // 其他消息推送过来是 MsgId MsgID int64 `xml:"MsgId"` // 其他消息推送过来是MsgId
TemplateMsgID int64 `xml:"MsgID"` // 模板消息推送成功的消息是 MsgID TemplateMsgID int64 `xml:"MsgID"` // 模板消息推送成功的消息是MsgID
Content string `xml:"Content"` Content string `xml:"Content"`
Recognition string `xml:"Recognition"` Recognition string `xml:"Recognition"`
PicURL string `xml:"PicUrl"` PicURL string `xml:"PicUrl"`
@@ -166,17 +166,17 @@ type MixMessage struct {
// 事件相关:发布能力 // 事件相关:发布能力
PublishEventInfo struct { PublishEventInfo struct {
PublishID int64 `xml:"publish_id"` // 发布任务 id PublishID int64 `xml:"publish_id"` // 发布任务id
PublishStatus freepublish.PublishStatus `xml:"publish_status"` // 发布状态 PublishStatus freepublish.PublishStatus `xml:"publish_status"` // 发布状态
ArticleID string `xml:"article_id"` // 当发布状态为 0 时(即成功)时,返回图文的 article_id可用于“客服消息”场景 ArticleID string `xml:"article_id"` // 当发布状态为0时(即成功)时,返回图文的 article_id可用于“客服消息”场景
ArticleDetail struct { ArticleDetail struct {
Count uint `xml:"count"` // 文章数量 Count uint `xml:"count"` // 文章数量
Item []struct { Item []struct {
Index uint `xml:"idx"` // 文章对应的编号 Index uint `xml:"idx"` // 文章对应的编号
ArticleURL string `xml:"article_url"` // 图文的永久链接 ArticleURL string `xml:"article_url"` // 图文的永久链接
} `xml:"item"` } `xml:"item"`
} `xml:"article_detail"` // 当发布状态为 0 时(即成功)时,返回内容 } `xml:"article_detail"` // 当发布状态为0时(即成功)时,返回内容
FailIndex []uint `xml:"fail_idx"` // 当发布状态为 2 或 4 时,返回不通过的文章编号,第一篇为 1其他发布状态则为空 FailIndex []uint `xml:"fail_idx"` // 当发布状态为2或4时,返回不通过的文章编号,第一篇为 1其他发布状态则为空
} `xml:"PublishEventInfo"` } `xml:"PublishEventInfo"`
// 第三方平台相关 // 第三方平台相关
@@ -222,19 +222,19 @@ type MixMessage struct {
TraceID string `xml:"trace_id"` TraceID string `xml:"trace_id"`
StatusCode int `xml:"status_code"` StatusCode int `xml:"status_code"`
// 小程序名称审核结果事件推送 //小程序名称审核结果事件推送
Ret int32 `xml:"ret"` // 审核结果 2失败3成功 Ret int32 `xml:"ret"` //审核结果 2失败3成功
NickName string `xml:"nickname"` // 小程序昵称 NickName string `xml:"nickname"` //小程序昵称
// 设备相关 // 设备相关
device.MsgDevice device.MsgDevice
// 小程序审核通知 //小程序审核通知
SuccTime int `xml:"SuccTime"` // 审核成功时的时间戳 SuccTime int `xml:"SuccTime"` //审核成功时的时间戳
FailTime int `xml:"FailTime"` // 审核不通过的时间戳 FailTime int `xml:"FailTime"` //审核不通过的时间戳
DelayTime int `xml:"DelayTime"` // 审核延后时的时间戳 DelayTime int `xml:"DelayTime"` //审核延后时的时间戳
Reason string `xml:"Reason"` // 审核不通过的原因 Reason string `xml:"Reason"` //审核不通过的原因
ScreenShot string `xml:"ScreenShot"` // 审核不通过的截图示例。用 | 分隔的 media_id 的列表,可通过获取永久素材接口拉取截图内容 ScreenShot string `xml:"ScreenShot"` //审核不通过的截图示例。用 | 分隔的 media_id 的列表,可通过获取永久素材接口拉取截图内容
} }
// SubscribeMsgPopupEvent 订阅通知事件推送的消息体 // SubscribeMsgPopupEvent 订阅通知事件推送的消息体
@@ -282,7 +282,7 @@ type ResponseEncryptedXMLMsg struct {
Nonce string `xml:"Nonce" json:"Nonce"` Nonce string `xml:"Nonce" json:"Nonce"`
} }
// CDATA 使用该类型在序列化为 xml 文本时文本会被解析器忽略 // CDATA 使用该类型,在序列化为 xml 文本时文本会被解析器忽略
type CDATA string type CDATA string
// MarshalXML 实现自己的序列化方法 // MarshalXML 实现自己的序列化方法

View File

@@ -1,7 +1,6 @@
package oauth package oauth
import ( import (
ctx2 "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
@@ -74,28 +73,11 @@ type ResAccessToken struct {
UnionID string `json:"unionid"` UnionID string `json:"unionid"`
} }
// GetUserInfoByCodeContext 通过网页授权的code 换取用户的信息
func (oauth *Oauth) GetUserInfoByCodeContext(ctx ctx2.Context, code string) (result UserInfo, err error) {
var (
token ResAccessToken
)
if token, err = oauth.GetUserAccessTokenContext(ctx, code); err != nil {
return
}
return oauth.GetUserInfoContext(ctx, token.AccessToken, token.OpenID, "")
}
// GetUserAccessToken 通过网页授权的code 换取access_token(区别于context中的access_token) // GetUserAccessToken 通过网页授权的code 换取access_token(区别于context中的access_token)
func (oauth *Oauth) GetUserAccessToken(code string) (result ResAccessToken, err error) { func (oauth *Oauth) GetUserAccessToken(code string) (result ResAccessToken, err error) {
return oauth.GetUserAccessTokenContext(ctx2.Background(), code)
}
// GetUserAccessTokenContext 通过网页授权的code 换取access_token(区别于context中的access_token) with context
func (oauth *Oauth) GetUserAccessTokenContext(ctx ctx2.Context, code string) (result ResAccessToken, err error) {
urlStr := fmt.Sprintf(accessTokenURL, oauth.AppID, oauth.AppSecret, code) urlStr := fmt.Sprintf(accessTokenURL, oauth.AppID, oauth.AppSecret, code)
var response []byte var response []byte
response, err = util.HTTPGetContext(ctx, urlStr) response, err = util.HTTPGet(urlStr)
if err != nil { if err != nil {
return return
} }
@@ -112,14 +94,9 @@ func (oauth *Oauth) GetUserAccessTokenContext(ctx ctx2.Context, code string) (re
// RefreshAccessToken 刷新access_token // RefreshAccessToken 刷新access_token
func (oauth *Oauth) RefreshAccessToken(refreshToken string) (result ResAccessToken, err error) { func (oauth *Oauth) RefreshAccessToken(refreshToken string) (result ResAccessToken, err error) {
return oauth.RefreshAccessTokenContext(ctx2.Background(), refreshToken)
}
// RefreshAccessTokenContext 刷新access_token with context
func (oauth *Oauth) RefreshAccessTokenContext(ctx ctx2.Context, refreshToken string) (result ResAccessToken, err error) {
urlStr := fmt.Sprintf(refreshAccessTokenURL, oauth.AppID, refreshToken) urlStr := fmt.Sprintf(refreshAccessTokenURL, oauth.AppID, refreshToken)
var response []byte var response []byte
response, err = util.HTTPGetContext(ctx, urlStr) response, err = util.HTTPGet(urlStr)
if err != nil { if err != nil {
return return
} }
@@ -136,14 +113,9 @@ func (oauth *Oauth) RefreshAccessTokenContext(ctx ctx2.Context, refreshToken str
// CheckAccessToken 检验access_token是否有效 // CheckAccessToken 检验access_token是否有效
func (oauth *Oauth) CheckAccessToken(accessToken, openID string) (b bool, err error) { func (oauth *Oauth) CheckAccessToken(accessToken, openID string) (b bool, err error) {
return oauth.CheckAccessTokenContext(ctx2.Background(), accessToken, openID)
}
// CheckAccessTokenContext 检验access_token是否有效 with context
func (oauth *Oauth) CheckAccessTokenContext(ctx ctx2.Context, accessToken, openID string) (b bool, err error) {
urlStr := fmt.Sprintf(checkAccessTokenURL, accessToken, openID) urlStr := fmt.Sprintf(checkAccessTokenURL, accessToken, openID)
var response []byte var response []byte
response, err = util.HTTPGetContext(ctx, urlStr) response, err = util.HTTPGet(urlStr)
if err != nil { if err != nil {
return return
} }
@@ -177,17 +149,12 @@ type UserInfo struct {
// GetUserInfo 如果scope为 snsapi_userinfo 则可以通过此方法获取到用户基本信息 // GetUserInfo 如果scope为 snsapi_userinfo 则可以通过此方法获取到用户基本信息
func (oauth *Oauth) GetUserInfo(accessToken, openID, lang string) (result UserInfo, err error) { func (oauth *Oauth) GetUserInfo(accessToken, openID, lang string) (result UserInfo, err error) {
return oauth.GetUserInfoContext(ctx2.Background(), accessToken, openID, lang)
}
// GetUserInfoContext 如果scope为 snsapi_userinfo 则可以通过此方法获取到用户基本信息 with context
func (oauth *Oauth) GetUserInfoContext(ctx ctx2.Context, accessToken, openID, lang string) (result UserInfo, err error) {
if lang == "" { if lang == "" {
lang = "zh_CN" lang = "zh_CN"
} }
urlStr := fmt.Sprintf(userInfoURL, accessToken, openID, lang) urlStr := fmt.Sprintf(userInfoURL, accessToken, openID, lang)
var response []byte var response []byte
response, err = util.HTTPGetContext(ctx, urlStr) response, err = util.HTTPGet(urlStr)
if err != nil { if err != nil {
return return
} }

View File

@@ -73,7 +73,7 @@ func (srv *Server) Serve() error {
if err != nil { if err != nil {
return err return err
} }
// 非安全模式下,请求处理方法返回为 nil 则直接回复 success 给微信服务器 // 非安全模式下请求处理方法返回为nil则直接回复success给微信服务器
if response == nil && !srv.isSafeMode { if response == nil && !srv.isSafeMode {
srv.String("success") srv.String("success")
return nil return nil
@@ -198,7 +198,7 @@ func (srv *Server) parseRequestMessage(rawXMLMsgBytes []byte) (msg *message.MixM
if err != nil { if err != nil {
return return
} }
// nonstandard json, 目前小程序订阅消息返回数据格式不标准,订阅消息模板单个 List 返回是对象,多个 List 返回是数组。 // nonstandard json, 目前小程序订阅消息返回数据格式不标准订阅消息模板单个List返回是对象多个List返回是数组。
if msg.MsgType == message.MsgTypeEvent { if msg.MsgType == message.MsgTypeEvent {
listData := gjson.Get(string(rawXMLMsgBytes), "List") listData := gjson.Get(string(rawXMLMsgBytes), "List")
if listData.IsObject() { if listData.IsObject() {
@@ -284,7 +284,7 @@ func (srv *Server) Send() (err error) {
if err != nil { if err != nil {
return return
} }
// TODO 如果获取不到 timestamp nonce 则自己生成 // TODO 如果获取不到timestamp nonce 则自己生成
timestamp := srv.timestamp timestamp := srv.timestamp
timestampStr := strconv.FormatInt(timestamp, 10) timestampStr := strconv.FormatInt(timestamp, 10)
msgSignature := util.Signature(srv.Token, timestampStr, srv.nonce, string(encryptedMsg)) msgSignature := util.Signature(srv.Token, timestampStr, srv.nonce, string(encryptedMsg))

View File

@@ -17,16 +17,6 @@ import (
"golang.org/x/crypto/pkcs12" "golang.org/x/crypto/pkcs12"
) )
// URIModifier URI修改器
type URIModifier func(uri string) string
var uriModifier URIModifier
// SetURIModifier 设置URI修改器
func SetURIModifier(fn URIModifier) {
uriModifier = fn
}
// HTTPGet get 请求 // HTTPGet get 请求
func HTTPGet(uri string) ([]byte, error) { func HTTPGet(uri string) ([]byte, error) {
return HTTPGetContext(context.Background(), uri) return HTTPGetContext(context.Background(), uri)
@@ -34,9 +24,6 @@ func HTTPGet(uri string) ([]byte, error) {
// HTTPGetContext get 请求 // HTTPGetContext get 请求
func HTTPGetContext(ctx context.Context, uri string) ([]byte, error) { func HTTPGetContext(ctx context.Context, uri string) ([]byte, error) {
if uriModifier != nil {
uri = uriModifier(uri)
}
request, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) request, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -60,9 +47,6 @@ func HTTPPost(uri string, data string) ([]byte, error) {
// HTTPPostContext post 请求 // HTTPPostContext post 请求
func HTTPPostContext(ctx context.Context, uri string, data []byte, header map[string]string) ([]byte, error) { func HTTPPostContext(ctx context.Context, uri string, data []byte, header map[string]string) ([]byte, error) {
if uriModifier != nil {
uri = uriModifier(uri)
}
body := bytes.NewBuffer(data) body := bytes.NewBuffer(data)
request, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, body) request, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, body)
if err != nil { if err != nil {
@@ -87,9 +71,6 @@ func HTTPPostContext(ctx context.Context, uri string, data []byte, header map[st
// PostJSONContext post json 数据请求 // PostJSONContext post json 数据请求
func PostJSONContext(ctx context.Context, uri string, obj interface{}) ([]byte, error) { func PostJSONContext(ctx context.Context, uri string, obj interface{}) ([]byte, error) {
if uriModifier != nil {
uri = uriModifier(uri)
}
jsonBuf := new(bytes.Buffer) jsonBuf := new(bytes.Buffer)
enc := json.NewEncoder(jsonBuf) enc := json.NewEncoder(jsonBuf)
enc.SetEscapeHTML(false) enc.SetEscapeHTML(false)
@@ -165,9 +146,6 @@ type MultipartFormField struct {
// PostMultipartForm 上传文件或其他多个字段 // PostMultipartForm 上传文件或其他多个字段
func PostMultipartForm(fields []MultipartFormField, uri string) (respBody []byte, err error) { func PostMultipartForm(fields []MultipartFormField, uri string) (respBody []byte, err error) {
if uriModifier != nil {
uri = uriModifier(uri)
}
bodyBuf := &bytes.Buffer{} bodyBuf := &bytes.Buffer{}
bodyWriter := multipart.NewWriter(bodyBuf) bodyWriter := multipart.NewWriter(bodyBuf)
@@ -220,9 +198,6 @@ func PostMultipartForm(fields []MultipartFormField, uri string) (respBody []byte
// PostXML perform a HTTP/POST request with XML body // PostXML perform a HTTP/POST request with XML body
func PostXML(uri string, obj interface{}) ([]byte, error) { func PostXML(uri string, obj interface{}) ([]byte, error) {
if uriModifier != nil {
uri = uriModifier(uri)
}
xmlData, err := xml.Marshal(obj) xmlData, err := xml.Marshal(obj)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -284,9 +259,6 @@ func pkcs12ToPem(p12 []byte, password string) tls.Certificate {
// PostXMLWithTLS perform a HTTP/POST request with XML body and TLS // PostXMLWithTLS perform a HTTP/POST request with XML body and TLS
func PostXMLWithTLS(uri string, obj interface{}, ca, key string) ([]byte, error) { func PostXMLWithTLS(uri string, obj interface{}, ca, key string) ([]byte, error) {
if uriModifier != nil {
uri = uriModifier(uri)
}
xmlData, err := xml.Marshal(obj) xmlData, err := xml.Marshal(obj)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -12,8 +12,7 @@ const (
// departmentSimpleListURL 获取子部门ID列表 // departmentSimpleListURL 获取子部门ID列表
departmentSimpleListURL = "https://qyapi.weixin.qq.com/cgi-bin/department/simplelist?access_token=%s&id=%d" departmentSimpleListURL = "https://qyapi.weixin.qq.com/cgi-bin/department/simplelist?access_token=%s&id=%d"
// departmentListURL 获取部门列表 // departmentListURL 获取部门列表
departmentListURL = "https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token=%s" departmentListURL = "https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token=%s"
departmentListByIDURL = "https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token=%s&id=%d"
// departmentGetURL 获取单个部门详情 https://qyapi.weixin.qq.com/cgi-bin/department/get?access_token=ACCESS_TOKEN&id=ID // departmentGetURL 获取单个部门详情 https://qyapi.weixin.qq.com/cgi-bin/department/get?access_token=ACCESS_TOKEN&id=ID
departmentGetURL = "https://qyapi.weixin.qq.com/cgi-bin/department/get?access_token=%s&id=%d" departmentGetURL = "https://qyapi.weixin.qq.com/cgi-bin/department/get?access_token=%s&id=%d"
) )
@@ -107,31 +106,13 @@ func (r *Client) DepartmentSimpleList(departmentID int) ([]*DepartmentID, error)
// DepartmentList 获取部门列表 // DepartmentList 获取部门列表
// @desc https://developer.work.weixin.qq.com/document/path/90208 // @desc https://developer.work.weixin.qq.com/document/path/90208
func (r *Client) DepartmentList() ([]*Department, error) { func (r *Client) DepartmentList() ([]*Department, error) {
return r.DepartmentListByID(0)
}
// DepartmentListByID 获取部门列表
//
// departmentID 部门id。获取指定部门及其下的子部门以及子部门的子部门等等递归
//
// @desc https://developer.work.weixin.qq.com/document/path/90208
func (r *Client) DepartmentListByID(departmentID int) ([]*Department, error) {
var formatURL string
// 获取accessToken // 获取accessToken
accessToken, err := r.GetAccessToken() accessToken, err := r.GetAccessToken()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if departmentID > 0 {
formatURL = fmt.Sprintf(departmentListByIDURL, accessToken, departmentID)
} else {
formatURL = fmt.Sprintf(departmentListURL, accessToken)
}
// 发起http请求 // 发起http请求
response, err := util.HTTPGet(formatURL) response, err := util.HTTPGet(fmt.Sprintf(departmentListURL, accessToken))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -13,14 +13,6 @@ const (
getDayDataURL = "https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckin_daydata?access_token=%s" getDayDataURL = "https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckin_daydata?access_token=%s"
// getMonthDataURL 获取打卡月报数据 // getMonthDataURL 获取打卡月报数据
getMonthDataURL = "https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckin_monthdata?access_token=%s" getMonthDataURL = "https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckin_monthdata?access_token=%s"
// getCorpOptionURL 获取企业所有打卡规则
getCorpOptionURL = "https://qyapi.weixin.qq.com/cgi-bin/checkin/getcorpcheckinoption?access_token=%s"
// getOptionURL 获取员工打卡规则
getOptionURL = "https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckinoption?access_token=%s"
// getScheduleListURL 获取打卡人员排班信息
getScheduleListURL = "https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckinschedulist?access_token=%s"
// getHardwareDataURL获取设备打卡数据
getHardwareDataURL = "https://qyapi.weixin.qq.com/cgi-bin/hardware/get_hardware_checkin_data?access_token=%s"
) )
type ( type (
@@ -265,396 +257,3 @@ func (r *Client) GetMonthData(req *GetCheckinDataRequest) (result *GetMonthDataR
err = util.DecodeWithError(response, result, "GetMonthData") err = util.DecodeWithError(response, result, "GetMonthData")
return return
} }
// GetCorpOptionResponse 获取企业所有打卡规则响应
type GetCorpOptionResponse struct {
util.CommonError
Group []CorpOptionGroup `json:"group"`
}
// CorpOptionGroup 企业规则信息列表
type CorpOptionGroup struct {
GroupType int64 `json:"grouptype"`
GroupID int64 `json:"groupid"`
GroupName string `json:"groupname"`
CheckinDate []GroupCheckinDate `json:"checkindate"`
SpeWorkdays []SpeWorkdays `json:"spe_workdays"`
SpeOffDays []SpeOffDays `json:"spe_offdays"`
SyncHolidays bool `json:"sync_holidays"`
NeedPhoto bool `json:"need_photo"`
NoteCanUseLocalPic bool `json:"note_can_use_local_pic"`
AllowCheckinOffWorkday bool `json:"allow_checkin_offworkday"`
AllowApplyOffWorkday bool `json:"allow_apply_offworkday"`
WifiMacInfos []WifiMacInfos `json:"wifimac_infos"`
LocInfos []LocInfos `json:"loc_infos"`
Range []Range `json:"range"`
CreateTime int64 `json:"create_time"`
WhiteUsers []string `json:"white_users"`
Type int64 `json:"type"`
ReporterInfo ReporterInfo `json:"reporterinfo"`
OtInfo GroupOtInfo `json:"ot_info"`
OtApplyInfo OtApplyInfo `json:"otapplyinfo"`
Uptime int64 `json:"uptime"`
AllowApplyBkCnt int64 `json:"allow_apply_bk_cnt"`
OptionOutRange int64 `json:"option_out_range"`
CreateUserID string `json:"create_userid"`
UseFaceDetect bool `json:"use_face_detect"`
AllowApplyBkDayLimit int64 `json:"allow_apply_bk_day_limit"`
UpdateUserID string `json:"update_userid"`
BukaRestriction int64 `json:"buka_restriction"`
ScheduleList []ScheduleList `json:"schedulelist"`
OffWorkIntervalTime int64 `json:"offwork_interval_time"`
}
// GroupCheckinDate 打卡时间,当规则类型为排班时没有意义
type GroupCheckinDate struct {
Workdays []int64 `json:"workdays"`
CheckinTime []GroupCheckinTime `json:"checkintime"`
NoNeedOffWork bool `json:"noneed_offwork"`
LimitAheadTime int64 `json:"limit_aheadtime"`
FlexOnDutyTime int64 `json:"flex_on_duty_time"`
FlexOffDutyTime int64 `json:"flex_off_duty_time"`
}
// GroupCheckinTime 工作日上下班打卡时间信息
type GroupCheckinTime struct {
WorkSec int64 `json:"work_sec"`
OffWorkSec int64 `json:"off_work_sec"`
RemindWorkSec int64 `json:"remind_work_sec"`
RemindOffWorkSec int64 `json:"remind_off_work_sec"`
}
// SpeWorkdays 特殊日期-必须打卡日期信息
type SpeWorkdays struct {
Timestamp int64 `json:"timestamp"`
Notes string `json:"notes"`
CheckinTime []GroupCheckinTime `json:"checkintime"`
}
// SpeOffDays 特殊日期-不用打卡日期信息
type SpeOffDays struct {
Timestamp int64 `json:"timestamp"`
Notes string `json:"notes"`
}
// WifiMacInfos 打卡地点-WiFi打卡信息
type WifiMacInfos struct {
WifiName string `json:"wifiname"`
WifiMac string `json:"wifimac"`
}
// LocInfos 打卡地点-位置打卡信息
type LocInfos struct {
Lat int64 `json:"lat"`
Lng int64 `json:"lng"`
LocTitle string `json:"loc_title"`
LocDetail string `json:"loc_detail"`
Distance int64 `json:"distance"`
}
// Range 打卡人员信息
type Range struct {
PartyID []string `json:"partyid"`
UserID []string `json:"userid"`
TagID []int64 `json:"tagid"`
}
// ReporterInfo 汇报对象信息
type ReporterInfo struct {
Reporters []Reporters `json:"reporters"`
UpdateTime int64 `json:"updatetime"`
}
// Reporters 汇报对象每个汇报人用userid表示
type Reporters struct {
UserID string `json:"userid"`
}
// GroupOtInfo 加班信息
type GroupOtInfo struct {
Type int64 `json:"type"`
AllowOtWorkingDay bool `json:"allow_ot_workingday"`
AllowOtNonWorkingDay bool `json:"allow_ot_nonworkingday"`
OtCheckInfo OtCheckInfo `json:"otcheckinfo"`
}
// OtCheckInfo 以打卡时间为准-加班时长计算规则信息
type OtCheckInfo struct {
OtWorkingDayTimeStart int64 `json:"ot_workingday_time_start"`
OtWorkingDayTimeMin int64 `json:"ot_workingday_time_min"`
OtWorkingDayTimeMax int64 `json:"ot_workingday_time_max"`
OtNonworkingDayTimeMin int64 `json:"ot_nonworkingday_time_min"`
OtNonworkingDayTimeMax int64 `json:"ot_nonworkingday_time_max"`
OtNonworkingDaySpanDayTime int64 `json:"ot_nonworkingday_spanday_time"`
OtWorkingDayRestInfo OtRestInfo `json:"ot_workingday_restinfo"`
OtNonWorkingDayRestInfo OtRestInfo `json:"ot_nonworkingday_restinfo"`
}
// OtRestInfo 加班-休息扣除配置信息
type OtRestInfo struct {
Type int64 `json:"type"`
FixTimeRule FixTimeRule `json:"fix_time_rule"`
CalOtTimeRule CalOtTimeRule `json:"cal_ottime_rule"`
}
// FixTimeRule 工作日加班-指定休息时间配置信息
type FixTimeRule struct {
FixTimeBeginSec int64 `json:"fix_time_begin_sec"`
FixTimeEndSec int64 `json:"fix_time_end_sec"`
}
// CalOtTimeRule 工作日加班-按加班时长扣除配置信息
type CalOtTimeRule struct {
Items []CalOtTimeRuleItem `json:"items"`
}
// CalOtTimeRuleItem 工作日加班-按加班时长扣除条件信息
type CalOtTimeRuleItem struct {
OtTime int64 `json:"ot_time"`
RestTime int64 `json:"rest_time"`
}
// OtApplyInfo 以加班申请核算打卡记录相关信息
type OtApplyInfo struct {
AllowOtWorkingDay bool `json:"allow_ot_workingday"`
AllowOtNonWorkingDay bool `json:"allow_ot_nonworkingday"`
Uiptime int64 `json:"uptime"`
OtNonworkingDaySpanDayTime int64 `json:"ot_nonworkingday_spanday_time"`
OtWorkingDayRestInfo OtRestInfo `json:"ot_workingday_restinfo"`
OtNonWorkingDayRestInfo OtRestInfo `json:"ot_nonworkingday_restinfo"`
}
// ScheduleList 排班信息列表
type ScheduleList struct {
ScheduleID int64 `json:"schedule_id"`
ScheduleName string `json:"schedule_name"`
TimeSection []TimeSection `json:"time_section"`
LimitAheadTime int64 `json:"limit_aheadtime"`
NoNeedOffWork bool `json:"noneed_offwork"`
LimitOffTime int64 `json:"limit_offtime"`
FlexOnDutyTime int64 `json:"flex_on_duty_time"`
FlexOffDutyTime int64 `json:"flex_off_duty_time"`
AllowFlex bool `json:"allow_flex"`
LateRule LateRule `json:"late_rule"`
MaxAllowArriveEarly int64 `json:"max_allow_arrive_early"`
MaxAllowArriveLate int64 `json:"max_allow_arrive_late"`
}
// TimeSection 班次上下班时段信息
type TimeSection struct {
TimeID int64 `json:"time_id"`
WorkSec int64 `json:"work_sec"`
OffWorkSec int64 `json:"off_work_sec"`
RemindWorkSec int64 `json:"remind_work_sec"`
RemindOffWorkSec int64 `json:"remind_off_work_sec"`
RestBeginTime int64 `json:"rest_begin_time"`
RestEndTime int64 `json:"rest_end_time"`
AllowRest bool `json:"allow_rest"`
}
// LateRule 晚走晚到时间规则信息
type LateRule struct {
AllowOffWorkAfterTime bool `json:"allow_offwork_after_time"`
TimeRules []TimeRule `json:"timerules"`
}
// TimeRule 迟到规则时间
type TimeRule struct {
OffWorkAfterTime int64 `json:"offwork_after_time"`
OnWorkFlexTime int64 `json:"onwork_flex_time"`
}
// GetCorpOption 获取企业所有打卡规则
// @see https://developer.work.weixin.qq.com/document/path/93384
func (r *Client) GetCorpOption() (*GetCorpOptionResponse, error) {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
var response []byte
if response, err = util.HTTPPost(fmt.Sprintf(getCorpOptionURL, accessToken), ""); err != nil {
return nil, err
}
result := &GetCorpOptionResponse{}
err = util.DecodeWithError(response, result, "GetCorpOption")
return result, err
}
// GetOptionRequest 获取员工打卡规则请求
type GetOptionRequest struct {
Datetime int64 `json:"datetime"`
UserIDList []string `json:"useridlist"`
}
// GetOptionResponse 获取员工打卡规则响应
type GetOptionResponse struct {
util.CommonError
Info []OptionInfo `json:"info"`
}
// OptionInfo 打卡规则列表
type OptionInfo struct {
UserID string `json:"userid"`
Group OptionGroup `json:"group"`
}
// OptionGroup 打卡规则相关信息
type OptionGroup struct {
GroupType int64 `json:"grouptype"`
GroupID int64 `json:"groupid"`
GroupName string `json:"groupname"`
CheckinDate []OptionCheckinDate `json:"checkindate"`
SpeWorkdays []SpeWorkdays `json:"spe_workdays"`
SpeOffDays []SpeOffDays `json:"spe_offdays"`
SyncHolidays bool `json:"sync_holidays"`
NeedPhoto bool `json:"need_photo"`
WifiMacInfos []WifiMacInfos `json:"wifimac_infos"`
NoteCanUseLocalPic bool `json:"note_can_use_local_pic"`
AllowCheckinOffWorkday bool `json:"allow_checkin_offworkday"`
AllowApplyOffWorkday bool `json:"allow_apply_offworkday"`
LocInfos []LocInfos `json:"loc_infos"`
ScheduleList []ScheduleList `json:"schedulelist"`
BukaRestriction int64 `json:"buka_restriction"`
}
// OptionCheckinDate 打卡时间配置
type OptionCheckinDate struct {
Workdays []int64 `json:"workdays"`
CheckinTime []GroupCheckinTime `json:"checkintime"`
FlexTime int64 `json:"flex_time"`
NoNeedOffWork bool `json:"noneed_offwork"`
LimitAheadTime int64 `json:"limit_aheadtime"`
FlexOnDutyTime int64 `json:"flex_on_duty_time"`
FlexOffDutyTime int64 `json:"flex_off_duty_time"`
}
// GetOption 获取员工打卡规则
// see https://developer.work.weixin.qq.com/document/path/90263
func (r *Client) GetOption(req *GetOptionRequest) (*GetOptionResponse, 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(getOptionURL, accessToken), req); err != nil {
return nil, err
}
result := &GetOptionResponse{}
err = util.DecodeWithError(response, result, "GetOption")
return result, err
}
// GetScheduleListRequest 获取打卡人员排班信息请求
type GetScheduleListRequest struct {
StartTime int64 `json:"starttime"`
EndTime int64 `json:"endtime"`
UserIDList []string `json:"useridlist"`
}
// GetScheduleListResponse 获取打卡人员排班信息响应
type GetScheduleListResponse struct {
util.CommonError
ScheduleList []ScheduleItem `json:"schedule_list"`
}
// ScheduleItem 排班表信息
type ScheduleItem struct {
UserID string `json:"userid"`
YearMonth int64 `json:"yearmonth"`
GroupID int64 `json:"groupid"`
GroupName string `json:"groupname"`
Schedule Schedule `json:"schedule"`
}
// Schedule 个人排班信息
type Schedule struct {
ScheduleList []ScheduleListItem `json:"scheduleList"`
}
// ScheduleListItem 个人排班表信息
type ScheduleListItem struct {
Day int64 `json:"day"`
ScheduleInfo ScheduleInfo `json:"schedule_info"`
}
// ScheduleInfo 个人当日排班信息
type ScheduleInfo struct {
ScheduleID int64 `json:"schedule_id"`
ScheduleName string `json:"schedule_name"`
TimeSection []ScheduleTimeSection `json:"time_section"`
}
// ScheduleTimeSection 班次上下班时段信息
type ScheduleTimeSection struct {
ID int64 `json:"id"`
WorkSec int64 `json:"work_sec"`
OffWorkSec int64 `json:"off_work_sec"`
RemindWorkSec int64 `json:"remind_work_sec"`
RemindOffWorkSec int64 `json:"remind_off_work_sec"`
}
// GetScheduleList 获取打卡人员排班信息
// see https://developer.work.weixin.qq.com/document/path/93380
func (r *Client) GetScheduleList(req *GetScheduleListRequest) (*GetScheduleListResponse, 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(getScheduleListURL, accessToken), req); err != nil {
return nil, err
}
result := &GetScheduleListResponse{}
err = util.DecodeWithError(response, result, "GetScheduleList")
return result, err
}
// GetHardwareDataRequest 获取设备打卡数据请求
type GetHardwareDataRequest struct {
FilterType int64 `json:"filter_type"`
StartTime int64 `json:"starttime"`
EndTime int64 `json:"endtime"`
UserIDList []string `json:"useridlist"`
}
// GetHardwareDataResponse 获取设备打卡数据响应
type GetHardwareDataResponse struct {
util.CommonError
CheckinData []HardwareCheckinData `json:"checkindata"`
}
// HardwareCheckinData 设备打卡数据
type HardwareCheckinData struct {
UserID string `json:"userid"`
CheckinTime int64 `json:"checkin_time"`
DeviceSn string `json:"device_sn"`
DeviceName string `json:"device_name"`
}
// GetHardwareData 获取设备打卡数据
// see https://developer.work.weixin.qq.com/document/path/94126
func (r *Client) GetHardwareData(req *GetHardwareDataRequest) (*GetHardwareDataResponse, 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(getHardwareDataURL, accessToken), req); err != nil {
return nil, err
}
result := &GetHardwareDataResponse{}
err = util.DecodeWithError(response, result, "GetHardwareData")
return result, err
}

View File

@@ -287,14 +287,14 @@ func (r *Client) SendWelcomeMsg(req *SendWelcomeMsgRequest) error {
// AddGroupWelcomeTemplateRequest 添加入群欢迎语素材请求 // AddGroupWelcomeTemplateRequest 添加入群欢迎语素材请求
type AddGroupWelcomeTemplateRequest struct { type AddGroupWelcomeTemplateRequest struct {
Text MsgText `json:"text"` Text MsgText `json:"text"`
Image *AttachmentImg `json:"image,omitempty"` Image AttachmentImg `json:"image"`
Link *AttachmentLink `json:"link,omitempty"` Link AttachmentLink `json:"link"`
MiniProgram *AttachmentMiniProgram `json:"miniprogram,omitempty"` MiniProgram AttachmentMiniProgram `json:"miniprogram"`
File *AttachmentFile `json:"file,omitempty"` File AttachmentFile `json:"file"`
Video *AttachmentVideo `json:"video,omitempty"` Video AttachmentVideo `json:"video"`
AgentID int `json:"agentid,omitempty"` AgentID int `json:"agentid"`
Notify int `json:"notify,omitempty"` Notify int `json:"notify"`
} }
// AddGroupWelcomeTemplateResponse 添加入群欢迎语素材响应 // AddGroupWelcomeTemplateResponse 添加入群欢迎语素材响应
@@ -324,14 +324,14 @@ func (r *Client) AddGroupWelcomeTemplate(req *AddGroupWelcomeTemplateRequest) (*
// EditGroupWelcomeTemplateRequest 编辑入群欢迎语素材请求 // EditGroupWelcomeTemplateRequest 编辑入群欢迎语素材请求
type EditGroupWelcomeTemplateRequest struct { type EditGroupWelcomeTemplateRequest struct {
TemplateID string `json:"template_id"` TemplateID string `json:"template_id"`
Text MsgText `json:"text"` Text MsgText `json:"text"`
Image *AttachmentImg `json:"image"` Image AttachmentImg `json:"image"`
Link *AttachmentLink `json:"link"` Link AttachmentLink `json:"link"`
MiniProgram *AttachmentMiniProgram `json:"miniprogram"` MiniProgram AttachmentMiniProgram `json:"miniprogram"`
File *AttachmentFile `json:"file"` File AttachmentFile `json:"file"`
Video *AttachmentVideo `json:"video"` Video AttachmentVideo `json:"video"`
AgentID int `json:"agentid"` AgentID int `json:"agentid"`
} }
// EditGroupWelcomeTemplateResponse 编辑入群欢迎语素材响应 // EditGroupWelcomeTemplateResponse 编辑入群欢迎语素材响应
@@ -366,11 +366,11 @@ type GetGroupWelcomeTemplateRequest struct {
type GetGroupWelcomeTemplateResponse struct { type GetGroupWelcomeTemplateResponse struct {
util.CommonError util.CommonError
Text MsgText `json:"text"` Text MsgText `json:"text"`
Image AttachmentImg `json:"image,omitempty"` Image AttachmentImg `json:"image"`
Link AttachmentLink `json:"link,omitempty"` Link AttachmentLink `json:"link"`
MiniProgram AttachmentMiniProgram `json:"miniprogram,omitempty"` MiniProgram AttachmentMiniProgram `json:"miniprogram"`
File AttachmentFile `json:"file,omitempty"` File AttachmentFile `json:"file"`
Video AttachmentVideo `json:"video,omitempty"` Video AttachmentVideo `json:"video"`
} }
// GetGroupWelcomeTemplate 获取入群欢迎语素材 // GetGroupWelcomeTemplate 获取入群欢迎语素材