Просмотр исходного кода

Merge branch 'v2' of github.com:silenceper/wechat into feature/remove-redis

houseme 2 лет назад
Родитель
Сommit
b797312d34
41 измененных файлов с 1325 добавлено и 611 удалено
  1. 2 2
      .github/workflows/go.yml
  2. 9 6
      credential/default_access_token.go
  3. 11 0
      doc/api/work.md
  4. 10 10
      domain/openapi/mgnt.go
  5. 2 2
      miniprogram/message/consts.go
  6. 2 2
      miniprogram/message/customer_message.go
  7. 234 30
      miniprogram/message/message.go
  8. 15 0
      miniprogram/message/reply.go
  9. 1 1
      miniprogram/minidrama/domain.go
  10. 30 30
      miniprogram/minidrama/minidrama.go
  11. 28 28
      miniprogram/virtualpayment/virtualpayment.go
  12. 22 22
      officialaccount/message/message.go
  13. 37 4
      officialaccount/oauth/oauth.go
  14. 3 3
      officialaccount/server/server.go
  15. 28 0
      util/http.go
  16. 28 17
      work/addresslist/department.go
  17. 10 20
      work/addresslist/linkedcorp.go
  18. 10 20
      work/addresslist/tag.go
  19. 12 28
      work/addresslist/user.go
  20. 2 4
      work/appchat/appchat.go
  21. 0 69
      work/checkin/checkin.go
  22. 660 0
      work/checkin/record.go
  23. 1 3
      work/externalcontact/callback.go
  24. 10 20
      work/externalcontact/contact_way.go
  25. 17 33
      work/externalcontact/customer_acquisition.go
  26. 12 29
      work/externalcontact/external_user.go
  27. 1 4
      work/externalcontact/follow_user.go
  28. 6 12
      work/externalcontact/groupchat.go
  29. 4 8
      work/externalcontact/join_way.go
  30. 22 44
      work/externalcontact/moment.go
  31. 51 57
      work/externalcontact/msg.go
  32. 3 12
      work/externalcontact/statistic.go
  33. 6 16
      work/externalcontact/tag.go
  34. 14 28
      work/externalcontact/transfer.go
  35. 4 8
      work/invoice/invoice.go
  36. 1 3
      work/kf/callback.go
  37. 4 8
      work/kf/statistic.go
  38. 6 12
      work/material/media.go
  39. 2 4
      work/message/message.go
  40. 1 4
      work/msgaudit/client_linux.go
  41. 4 8
      work/oauth/oauth.go

+ 2 - 2
.github/workflows/go.yml

@@ -2,9 +2,9 @@ name: Go
 
 on:
   push:
-    branches: [ master,release-*,v2 ]
+    branches: [ master,release-*,v2,feature/** ]
   pull_request:
-    branches: [ master,release-*,v2 ]
+    branches: [ master,release-*,v2,feature/** ]
 
 jobs:
   golangci:

+ 9 - 6
credential/default_access_token.go

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

+ 11 - 0
doc/api/work.md

@@ -90,10 +90,12 @@ host: https://qyapi.weixin.qq.com/
 |    名称     | 请求方式 | URL                                     | 是否已实现   | 使用方法                            | 贡献者      |
 |:---------:|------|:----------------------------------------| ---------- | -------------------------------   |----------|
 | 获取子部门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  |
 |  获取成员ID列表   | Post | /cgi-bin/user/list_id                | YES        | (r *Client) UserListId  | MARKWANG  |
 
 
+
 ## 素材管理
 [官方文档](https://developer.work.weixin.qq.com/document/path/91054)
 
@@ -116,5 +118,14 @@ host: https://qyapi.weixin.qq.com/
 | ---------------- | -------- | --------------------- | ---------- | -------------------------- | -------- |
 | 群机器人发送消息 | POST     | /cgi-bin/webhook/send | YES        | (r *Client) RobotBroadcast | chcthink |
 
+## 打卡
+
+[官方文档](https://developer.work.weixin.qq.com/document/path/96497)
+
+| 名称       | 请求方式 | URL                   | 是否已实现 | 使用方法                   | 贡献者     |
+|----------| -------- | --------------------- | ---------- | -------------------------- |---------|
+| 获取打卡日报数据 | POST     |  /cgi-bin/checkin/getcheckin_daydata | YES        | (r *Client) GetDayData | Thinker |
+| 获取打卡月报数据 | POST     |  /cgi-bin/checkin/getcheckin_monthdata | YES        | (r *Client) GetMonthData | Thinker |
+
 ## 应用管理
 TODO

+ 10 - 10
domain/openapi/mgnt.go

@@ -2,12 +2,12 @@ package openapi
 
 import "github.com/silenceper/wechat/v2/util"
 
-// GetAPIQuotaParams 查询API调用额度参数
+// GetAPIQuotaParams 查询 API 调用额度参数
 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 {
 	util.CommonError
 	Quota struct {
@@ -17,20 +17,20 @@ type APIQuota struct {
 	} `json:"quota"` // 详情
 }
 
-// GetRidInfoParams 查询rid信息参数
+// GetRidInfoParams 查询 rid 信息参数
 type GetRidInfoParams struct {
-	Rid string `json:"rid"` // 调用接口报错返回的rid
+	Rid string `json:"rid"` // 调用接口报错返回的 rid
 }
 
-// RidInfo rid信息
+// RidInfo rid 信息
 type RidInfo struct {
 	util.CommonError
 	Request struct {
 		InvokeTime   int64  `json:"invoke_time"`   // 发起请求的时间戳
 		CostInMs     int64  `json:"cost_in_ms"`    // 请求毫秒级耗时
-		RequestURL   string `json:"request_url"`   // 请求的URL参数
-		RequestBody  string `json:"request_body"`  // post请求的请求参数
+		RequestURL   string `json:"request_url"`   // 请求的 URL 参数
+		RequestBody  string `json:"request_body"`  // post 请求的请求参数
 		ResponseBody string `json:"response_body"` // 接口请求返回参数
-		ClientIP     string `json:"client_ip"`     // 接口请求的客户端ip
-	} `json:"request"` // 该rid对应的请求详情
+		ClientIP     string `json:"client_ip"`     // 接口请求的客户端 ip
+	} `json:"request"` // 该 rid 对应的请求详情
 }

+ 2 - 2
miniprogram/message/consts.go

@@ -22,9 +22,9 @@ const (
 	MsgTypeMiniProgramPage = "miniprogrampage"
 	// MsgTypeEvent 事件
 	MsgTypeEvent MsgType = "event"
-	// DataTypeXML XML格式数据
+	// DataTypeXML XML 格式数据
 	DataTypeXML = "xml"
-	// DataTypeJSON JSON格式数据
+	// DataTypeJSON JSON 格式数据
 	DataTypeJSON = "json"
 )
 

+ 2 - 2
miniprogram/message/customer_message.go

@@ -28,7 +28,7 @@ type MediaText struct {
 	Content string `json:"content"`
 }
 
-// MediaResource  消息使用的临时素材id
+// MediaResource  消息使用的临时素材 id
 type MediaResource struct {
 	MediaID string `json:"media_id"`
 }
@@ -51,7 +51,7 @@ type MediaLink struct {
 
 // CustomerMessage  客服消息
 type CustomerMessage struct {
-	ToUser          string                `json:"touser"`                    // 接受者OpenID
+	ToUser          string                `json:"touser"`                    // 接受者 OpenID
 	Msgtype         MsgType               `json:"msgtype"`                   // 客服消息类型
 	Text            *MediaText            `json:"text,omitempty"`            // 可选
 	Image           *MediaResource        `json:"image,omitempty"`           // 可选

+ 234 - 30
miniprogram/message/message.go

@@ -9,6 +9,8 @@ import (
 	"sort"
 	"strings"
 
+	"github.com/tidwall/gjson"
+
 	"github.com/silenceper/wechat/v2/miniprogram/context"
 	"github.com/silenceper/wechat/v2/miniprogram/security"
 	"github.com/silenceper/wechat/v2/util"
@@ -18,11 +20,11 @@ import (
 type ConfirmReceiveMethod int8
 
 const (
-	// EventTypeTradeManageRemindAccessAPI 提醒接入发货信息管理服务API
+	// EventTypeTradeManageRemindAccessAPI 提醒接入发货信息管理服务 API
 	// 小程序完成账期授权时/小程序产生第一笔交易时/已产生交易但从未发货的小程序,每天一次
 	EventTypeTradeManageRemindAccessAPI EventType = "trade_manage_remind_access_api"
 	// EventTypeTradeManageRemindShipping 提醒需要上传发货信息
-	// 曾经发过货的小程序,订单超过48小时未发货时
+	// 曾经发过货的小程序,订单超过 48 小时未发货时
 	EventTypeTradeManageRemindShipping EventType = "trade_manage_remind_shipping"
 	// EventTypeTradeManageOrderSettlement 订单将要结算或已经结算
 	// 订单完成发货时/订单结算时
@@ -39,14 +41,27 @@ const (
 	EventTypeXpayGoodsDeliverNotify EventType = "xpay_goods_deliver_notify"
 	// EventTypeXpayCoinPayNotify 代币支付推送事件
 	EventTypeXpayCoinPayNotify EventType = "xpay_coin_pay_notify"
+	// EventSubscribePopup 用户操作订阅通知弹窗事件推送,用户在图文等场景内订阅通知的操作
+	EventSubscribePopup EventType = "subscribe_msg_popup_event"
+	// EventSubscribeMsgChange 用户管理订阅通知,用户在服务通知管理页面做通知管理时的操作
+	EventSubscribeMsgChange EventType = "subscribe_msg_change_event"
+	// EventSubscribeMsgSent 发送订阅通知,调用 bizsend 接口发送通知
+	EventSubscribeMsgSent EventType = "subscribe_msg_sent_event"
 	// ConfirmReceiveMethodAuto 自动确认收货
 	ConfirmReceiveMethodAuto ConfirmReceiveMethod = 1
 	// ConfirmReceiveMethodManual 手动确认收货
 	ConfirmReceiveMethodManual ConfirmReceiveMethod = 2
 )
 
+const (
+	// InfoTypeAcceptSubscribeMessage 接受订阅通知
+	InfoTypeAcceptSubscribeMessage InfoType = "accept"
+	// InfoTypeRejectSubscribeMessage 拒绝订阅通知
+	InfoTypeRejectSubscribeMessage InfoType = "reject"
+)
+
 // PushReceiver 接收消息推送
-// 暂仅支付Aes加密方式
+// 暂仅支付 Aes 加密方式
 type PushReceiver struct {
 	*context.Context
 }
@@ -58,16 +73,16 @@ func NewPushReceiver(ctx *context.Context) *PushReceiver {
 	}
 }
 
-// GetMsg 获取接收到的消息(如果是加密的返回解密数据)
+// GetMsg 获取接收到的消息 (如果是加密的返回解密数据)
 func (receiver *PushReceiver) GetMsg(r *http.Request) (string, []byte, error) {
 	// 判断请求格式
 	var dataType string
 	contentType := r.Header.Get("Content-Type")
 	if strings.HasPrefix(contentType, "text/xml") {
-		// xml格式
+		// xml 格式
 		dataType = DataTypeXML
 	} else {
-		// json格式
+		// json 格式
 		dataType = DataTypeJSON
 	}
 
@@ -108,7 +123,7 @@ func (receiver *PushReceiver) GetMsg(r *http.Request) (string, []byte, error) {
 	return dataType, byteData, err
 }
 
-// GetMsgData 获取接收到的消息(解密数据)
+// GetMsgData 获取接收到的消息 (解密数据)
 func (receiver *PushReceiver) GetMsgData(r *http.Request) (MsgType, EventType, PushData, error) {
 	dataType, decryptMsg, err := receiver.GetMsg(r)
 	if err != nil {
@@ -144,7 +159,7 @@ func (receiver *PushReceiver) GetMsgData(r *http.Request) (MsgType, EventType, P
 func (receiver *PushReceiver) getEvent(dataType string, eventType EventType, decryptMsg []byte) (PushData, error) {
 	switch eventType {
 	case EventTypeTradeManageRemindAccessAPI:
-		// 提醒接入发货信息管理服务API
+		// 提醒接入发货信息管理服务 API
 		var pushData PushDataRemindAccessAPI
 		err := receiver.unmarshal(dataType, decryptMsg, &pushData)
 		return &pushData, err
@@ -188,25 +203,104 @@ func (receiver *PushReceiver) getEvent(dataType string, eventType EventType, dec
 		var pushData PushDataXpayCoinPayNotify
 		err := receiver.unmarshal(dataType, decryptMsg, &pushData)
 		return &pushData, err
+	case EventSubscribePopup:
+		// 用户操作订阅通知弹窗事件推送
+		return receiver.unmarshalSubscribePopup(dataType, decryptMsg)
+	case EventSubscribeMsgChange:
+		// 用户管理订阅通知事件推送
+		return receiver.unmarshalSubscribeMsgChange(dataType, decryptMsg)
+	case EventSubscribeMsgSent:
+		// 用户发送订阅通知事件推送
+		return receiver.unmarshalSubscribeMsgSent(dataType, decryptMsg)
 	}
 	// 暂不支持其他事件类型,直接返回解密后的数据,由调用方处理
 	return decryptMsg, nil
 }
 
 // unmarshal 解析推送的数据
-func (receiver *PushReceiver) unmarshal(dateType string, decryptMsg []byte, pushData interface{}) error {
-	if dateType == DataTypeXML {
+func (receiver *PushReceiver) unmarshal(dataType string, decryptMsg []byte, pushData interface{}) error {
+	if dataType == DataTypeXML {
 		return xml.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 接收到的数据
 type DataReceived struct {
 	Encrypt string `json:"Encrypt" xml:"Encrypt"` // 加密的消息体
 }
 
-// PushData 推送的数据(已转对应的结构体)
+// PushData 推送的数据 (已转对应的结构体)
 type PushData interface{}
 
 // CommonPushData 推送数据通用部分
@@ -216,7 +310,7 @@ type CommonPushData struct {
 	Event        EventType `json:"Event" xml:"Event"`               // 事件类型
 	ToUserName   string    `json:"ToUserName" xml:"ToUserName"`     // 小程序的原始 ID
 	FromUserName string    `json:"FromUserName" xml:"FromUserName"` // 发送方账号(一个 OpenID,此时发送方是系统账号)
-	CreateTime   int64     `json:"CreateTime" xml:"CreateTime"`     // 消息创建时间 (整型),时间戳
+	CreateTime   int64     `json:"CreateTime" xml:"CreateTime"`     // 消息创建时间(整型),时间戳
 }
 
 // MediaCheckAsyncData 媒体内容安全异步审查结果通知
@@ -272,7 +366,7 @@ type PushDataRemindShipping struct {
 	Msg             string `json:"msg" xml:"msg"`                             // 消息文本内容
 }
 
-// PushDataRemindAccessAPI 提醒接入发货信息管理服务API信息
+// PushDataRemindAccessAPI 提醒接入发货信息管理服务 API 信息
 type PushDataRemindAccessAPI struct {
 	CommonPushData
 	Msg string `json:"msg" xml:"msg"` // 消息文本内容
@@ -281,9 +375,9 @@ type PushDataRemindAccessAPI struct {
 // PushDataAddExpressPath 运单轨迹更新信息
 type PushDataAddExpressPath struct {
 	CommonPushData
-	DeliveryID string                          `json:"DeliveryID" xml:"DeliveryID"` // 快递公司ID
-	WayBillID  string                          `json:"WaybillId" xml:"WaybillId"`   // 运单ID
-	OrderID    string                          `json:"OrderId" xml:"OrderId"`       // 订单ID
+	DeliveryID string                          `json:"DeliveryID" xml:"DeliveryID"` // 快递公司 ID
+	WayBillID  string                          `json:"WaybillId" xml:"WaybillId"`   // 运单 ID
+	OrderID    string                          `json:"OrderId" xml:"OrderId"`       // 订单 ID
 	Version    int                             `json:"Version" xml:"Version"`       // 轨迹版本号(整型)
 	Count      int                             `json:"Count" xml:"Count"`           // 轨迹节点数(整型)
 	Actions    []*PushDataAddExpressPathAction `json:"Actions" xml:"Actions"`       // 轨迹节点列表
@@ -304,10 +398,10 @@ type PushDataSecVodUpload struct {
 
 // SecVodUploadEvent 短剧媒资上传完成事件
 type SecVodUploadEvent struct {
-	MediaID       string `json:"media_id" xml:"media_id"`             // 媒资id
+	MediaID       string `json:"media_id" xml:"media_id"`             // 媒资 id
 	SourceContext string `json:"source_context" xml:"source_context"` // 透传上传接口中开发者设置的值。
-	Errcode       int    `json:"errcode" xml:"errcode"`               // 错误码,上传失败时该值非
-	Errmsg        string `json:"errmsg" xml:"errmsg"`                 // 错误提示
+	ErrCode       int    `json:"errcode" xml:"errcode"`               // 错误码,上传失败时该值非
+	ErrMsg        string `json:"errmsg" xml:"errmsg"`                 // 错误提示
 }
 
 // PushDataSecVodAudit 短剧媒资审核状态
@@ -318,14 +412,14 @@ type PushDataSecVodAudit struct {
 
 // SecVodAuditEvent 短剧媒资审核状态事件
 type SecVodAuditEvent struct {
-	DramaID       string           `json:"drama_id" xml:"drama_id"`             // 剧目id
+	DramaID       string           `json:"drama_id" xml:"drama_id"`             // 剧目 id
 	SourceContext string           `json:"source_context" xml:"source_context"` // 透传上传接口中开发者设置的值
-	AuditDetail   DramaAuditDetail `json:"audit_detail" xml:"audit_detail"`     // 剧目审核结果,单独每一集的审核结果可以根据drama_id查询剧集详情得到
+	AuditDetail   DramaAuditDetail `json:"audit_detail" xml:"audit_detail"`     // 剧目审核结果,单独每一集的审核结果可以根据 drama_id 查询剧集详情得到
 }
 
 // DramaAuditDetail 剧目审核结果
 type DramaAuditDetail struct {
-	Status     int   `json:"status" xml:"status"`           // 审核状态,0为无效值;1为审核中;2为最终失败;3为审核通过;4为驳回重填
+	Status     int   `json:"status" xml:"status"`           // 审核状态,0 为无效值;1 为审核中;2 为最终失败;3 为审核通过;4 为驳回重填
 	CreateTime int64 `json:"create_time" xml:"create_time"` // 提审时间戳
 	AuditTime  int64 `json:"audit_time" xml:"audit_time"`   // 审核时间戳
 }
@@ -333,9 +427,9 @@ type DramaAuditDetail struct {
 // PushDataXpayGoodsDeliverNotify 道具发货推送
 type PushDataXpayGoodsDeliverNotify struct {
 	CommonPushData
-	OpenID        string        `json:"OpenId" xml:"OpenId"`               // 用户openid
+	OpenID        string        `json:"OpenId" xml:"OpenId"`               // 用户 openid
 	OutTradeNo    string        `json:"OutTradeNo" xml:"OutTradeNo"`       // 业务订单号
-	Env           int           `json:"Env" xml:"Env"`                     //,环境配置 0:现网环境(也叫正式环境)1:沙箱环境
+	Env           int           `json:"Env" xml:"Env"`                     // ,环境配置 0:现网环境(也叫正式环境)1:沙箱环境
 	WeChatPayInfo WeChatPayInfo `json:"WeChatPayInfo" xml:"WeChatPayInfo"` // 微信支付信息 非微信支付渠道可能没有
 	GoodsInfo     GoodsInfo     `json:"GoodsInfo" xml:"GoodsInfo"`         // 道具参数信息
 }
@@ -344,14 +438,14 @@ type PushDataXpayGoodsDeliverNotify struct {
 type WeChatPayInfo struct {
 	MchOrderNo    string `json:"MchOrderNo" xml:"MchOrderNo"`       // 微信支付商户单号
 	TransactionID string `json:"TransactionId" xml:"TransactionId"` // 交易单号(微信支付订单号)
-	PaidTime      int64  `json:"PaidTime" xml:"PaidTime"`           // 用户支付时间,Linux秒级时间戳
+	PaidTime      int64  `json:"PaidTime" xml:"PaidTime"`           // 用户支付时间,Linux 秒级时间戳
 }
 
 // GoodsInfo 道具参数信息
 type GoodsInfo struct {
-	ProductID   string `json:"ProductId" xml:"ProductId"`     // 道具ID
+	ProductID   string `json:"ProductId" xml:"ProductId"`     // 道具 ID
 	Quantity    int    `json:"Quantity" xml:"Quantity"`       // 数量
-	OrigPrice   int64  `json:"OrigPrice" xml:"OrigPrice"`     // 物品原始价格 (单位:分)
+	OrigPrice   int64  `json:"OrigPrice" xml:"OrigPrice"`     // 物品原始价格(单位:分)
 	ActualPrice int64  `json:"ActualPrice" xml:"ActualPrice"` // 物品实际支付价格(单位:分)
 	Attach      string `json:"Attach" xml:"Attach"`           // 透传信息
 }
@@ -359,9 +453,9 @@ type GoodsInfo struct {
 // PushDataXpayCoinPayNotify 代币支付推送
 type PushDataXpayCoinPayNotify struct {
 	CommonPushData
-	OpenID        string        `json:"OpenId" xml:"OpenId"`               // 用户openid
+	OpenID        string        `json:"OpenId" xml:"OpenId"`               // 用户 openid
 	OutTradeNo    string        `json:"OutTradeNo" xml:"OutTradeNo"`       // 业务订单号
-	Env           int           `json:"Env" xml:"Env"`                     //,环境配置 0:现网环境(也叫正式环境)1:沙箱环境
+	Env           int           `json:"Env" xml:"Env"`                     // ,环境配置 0:现网环境(也叫正式环境)1:沙箱环境
 	WeChatPayInfo WeChatPayInfo `json:"WeChatPayInfo" xml:"WeChatPayInfo"` // 微信支付信息 非微信支付渠道可能没有
 	CoinInfo      CoinInfo      `json:"CoinInfo" xml:"CoinInfo"`           // 代币参数信息
 }
@@ -369,7 +463,117 @@ type PushDataXpayCoinPayNotify struct {
 // CoinInfo 代币参数信息
 type CoinInfo struct {
 	Quantity    int    `json:"Quantity" xml:"Quantity"`       // 数量
-	OrigPrice   int64  `json:"OrigPrice" xml:"OrigPrice"`     // 物品原始价格 (单位:分)
+	OrigPrice   int64  `json:"OrigPrice" xml:"OrigPrice"`     // 物品原始价格(单位:分)
 	ActualPrice int64  `json:"ActualPrice" xml:"ActualPrice"` // 物品实际支付价格(单位:分)
 	Attach      string `json:"Attach" xml:"Attach"`           // 透传信息
 }
+
+// PushDataSubscribePopup 用户操作订阅通知弹窗事件推送
+type PushDataSubscribePopup struct {
+	CommonPushData
+	subscribeMsgPopupEventList []SubscribeMsgPopupEventList `json:"-"`
+	SubscribeMsgPopupEvent     SubscribeMsgPopupEvent       `xml:"SubscribeMsgPopupEvent"`
+}
+
+// SubscribeMsgPopupEvent 用户操作订阅通知弹窗消息回调
+type SubscribeMsgPopupEvent struct {
+	List []SubscribeMsgPopupEventList `xml:"List"`
+}
+
+// SubscribeMsgPopupEventList 订阅消息事件列表
+type SubscribeMsgPopupEventList struct {
+	TemplateID            string `xml:"TemplateId" json:"TemplateId"`
+	SubscribeStatusString string `xml:"SubscribeStatusString" json:"SubscribeStatusString"`
+	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 用户管理订阅通知事件推送
+type PushDataSubscribeMsgChange struct {
+	CommonPushData
+	SubscribeMsgChangeEvent SubscribeMsgChangeEvent  `xml:"SubscribeMsgChangeEvent"`
+	subscribeMsgChangeList  []SubscribeMsgChangeList `json:"-"`
+}
+
+// SubscribeMsgChangeEvent 用户管理订阅通知回调
+type SubscribeMsgChangeEvent struct {
+	List []SubscribeMsgChangeList `xml:"List" json:"List"`
+}
+
+// SubscribeMsgChangeList 订阅消息事件列表
+type SubscribeMsgChangeList struct {
+	TemplateID            string `xml:"TemplateId" json:"TemplateId"`
+	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 用户发送订阅通知事件推送
+type PushDataSubscribeMsgSent struct {
+	CommonPushData
+	SubscribeMsgSentEvent     SubscribeMsgSentEvent  `xml:"SubscribeMsgSentEvent"`
+	subscribeMsgSentEventList []SubscribeMsgSentList `json:"-"`
+}
+
+// SubscribeMsgSentEvent 用户发送订阅通知回调
+type SubscribeMsgSentEvent struct {
+	List []SubscribeMsgSentList `xml:"List" json:"List"`
+}
+
+// SubscribeMsgSentList 订阅消息事件列表
+type SubscribeMsgSentList struct {
+	TemplateID  string `xml:"TemplateId" json:"TemplateId"`
+	MsgID       string `xml:"MsgID" json:"MsgID"`
+	ErrorCode   int    `xml:"ErrorCode" json:"ErrorCode"`
+	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
+}

+ 15 - 0
miniprogram/message/reply.go

@@ -0,0 +1,15 @@
+package message
+
+import "errors"
+
+// ErrInvalidReply 无效的回复
+var ErrInvalidReply = errors.New("无效的回复信息")
+
+// ErrUnsupportedReply 不支持的回复类型
+var ErrUnsupportedReply = errors.New("不支持的回复消息")
+
+// Reply 消息回复
+type Reply struct {
+	MsgType MsgType
+	MsgData interface{}
+}

+ 1 - 1
miniprogram/minidrama/domain.go

@@ -180,7 +180,7 @@ type MediaInfo struct {
 	CreateTime  int64             `json:"create_time"`  // 	上传时间,时间戳。
 	ExpireTime  int64             `json:"expire_time"`  // 过期时间,时间戳。
 	DramaID     int64             `json:"drama_id"`     // 所属剧目 id。
-	FileSize    string            `json:"file_size"`    // 媒资文件大小,单位:字节。
+	FileSize    int64             `json:"file_size"`    // 媒资文件大小,单位:字节。
 	Duration    int64             `json:"duration"`     // 播放时长,单位:秒。
 	Name        string            `json:"name"`         // 媒资文件名。
 	Description string            `json:"description"`  // 描述。

+ 30 - 30
miniprogram/minidrama/minidrama.go

@@ -27,7 +27,7 @@ import (
 )
 
 // SingleFileUpload 单文件上传
-func (s *MiniDrama) SingleFileUpload(ctx context.Context, in *SingleFileUploadRequest) (out *SingleFileUploadResponse, err error) {
+func (s *MiniDrama) SingleFileUpload(ctx context.Context, in *SingleFileUploadRequest) (out SingleFileUploadResponse, err error) {
 	var address string
 	if address, err = s.requestAddress(ctx, singleFileUpload); err != nil {
 		return
@@ -76,12 +76,12 @@ func (s *MiniDrama) SingleFileUpload(ctx context.Context, in *SingleFileUploadRe
 		return
 	}
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "SingleFileUpload")
+	err = util.DecodeWithError(response, &out, "SingleFileUpload")
 	return
 }
 
 // PullUpload 拉取上传
-func (s *MiniDrama) PullUpload(ctx context.Context, in *PullUploadRequest) (out *PullUploadResponse, err error) {
+func (s *MiniDrama) PullUpload(ctx context.Context, in *PullUploadRequest) (out PullUploadResponse, err error) {
 	var address string
 	if address, err = s.requestAddress(ctx, pullUpload); err != nil {
 		return
@@ -92,12 +92,12 @@ func (s *MiniDrama) PullUpload(ctx context.Context, in *PullUploadRequest) (out
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "PullUpload")
+	err = util.DecodeWithError(response, &out, "PullUpload")
 	return
 }
 
 // GetTask 查询任务状态
-func (s *MiniDrama) GetTask(ctx context.Context, in *GetTaskRequest) (out *GetTaskResponse, err error) {
+func (s *MiniDrama) GetTask(ctx context.Context, in *GetTaskRequest) (out GetTaskResponse, err error) {
 	var address string
 	if address, err = s.requestAddress(ctx, getTask); err != nil {
 		return
@@ -109,12 +109,12 @@ func (s *MiniDrama) GetTask(ctx context.Context, in *GetTaskRequest) (out *GetTa
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "GetTask")
+	err = util.DecodeWithError(response, &out, "GetTask")
 	return
 }
 
 // ApplyUpload 申请分片上传
-func (s *MiniDrama) ApplyUpload(ctx context.Context, in *ApplyUploadRequest) (out *ApplyUploadResponse, err error) {
+func (s *MiniDrama) ApplyUpload(ctx context.Context, in *ApplyUploadRequest) (out ApplyUploadResponse, err error) {
 	var address string
 	if address, err = s.requestAddress(ctx, applyUpload); err != nil {
 		return
@@ -126,13 +126,13 @@ func (s *MiniDrama) ApplyUpload(ctx context.Context, in *ApplyUploadRequest) (ou
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "ApplyUpload")
+	err = util.DecodeWithError(response, &out, "ApplyUpload")
 	return
 }
 
 // UploadPart 上传分片
 // Content-Type 需要指定为 multipart/form-data; boundary=<delimiter>,<箭头括号>表示必须替换为有效值的变量。
-func (s *MiniDrama) UploadPart(ctx context.Context, in *UploadPartRequest) (out *UploadPartResponse, err error) {
+func (s *MiniDrama) UploadPart(ctx context.Context, in *UploadPartRequest) (out UploadPartResponse, err error) {
 	var address string
 	if address, err = s.requestAddress(ctx, uploadPart); err != nil {
 		return
@@ -165,12 +165,12 @@ func (s *MiniDrama) UploadPart(ctx context.Context, in *UploadPartRequest) (out
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "UploadPart")
+	err = util.DecodeWithError(response, &out, "UploadPart")
 	return
 }
 
 // CommitUpload 确认上传
-func (s *MiniDrama) CommitUpload(ctx context.Context, in *CommitUploadRequest) (out *CommitUploadResponse, err error) {
+func (s *MiniDrama) CommitUpload(ctx context.Context, in *CommitUploadRequest) (out CommitUploadResponse, err error) {
 	var address string
 	if address, err = s.requestAddress(ctx, commitUpload); err != nil {
 		return
@@ -182,12 +182,12 @@ func (s *MiniDrama) CommitUpload(ctx context.Context, in *CommitUploadRequest) (
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "CommitUpload")
+	err = util.DecodeWithError(response, &out, "CommitUpload")
 	return
 }
 
 // ListMedia 获取媒体列表
-func (s *MiniDrama) ListMedia(ctx context.Context, in *ListMediaRequest) (out *ListMediaResponse, err error) {
+func (s *MiniDrama) ListMedia(ctx context.Context, in *ListMediaRequest) (out ListMediaResponse, err error) {
 	var address string
 	if address, err = s.requestAddress(ctx, listMedia); err != nil {
 		return
@@ -199,12 +199,12 @@ func (s *MiniDrama) ListMedia(ctx context.Context, in *ListMediaRequest) (out *L
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "ListMedia")
+	err = util.DecodeWithError(response, &out, "ListMedia")
 	return
 }
 
 // GetMedia 获取媒资详细信息
-func (s *MiniDrama) GetMedia(ctx context.Context, in *GetMediaRequest) (out *GetMediaResponse, err error) {
+func (s *MiniDrama) GetMedia(ctx context.Context, in *GetMediaRequest) (out GetMediaResponse, err error) {
 	var address string
 	if address, err = s.requestAddress(ctx, getMedia); err != nil {
 		return
@@ -216,12 +216,12 @@ func (s *MiniDrama) GetMedia(ctx context.Context, in *GetMediaRequest) (out *Get
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "GetMedia")
+	err = util.DecodeWithError(response, &out, "GetMedia")
 	return
 }
 
 // GetMediaLink 获取媒资播放链接
-func (s *MiniDrama) GetMediaLink(ctx context.Context, in *GetMediaLinkRequest) (out *GetMediaLinkResponse, err error) {
+func (s *MiniDrama) GetMediaLink(ctx context.Context, in *GetMediaLinkRequest) (out GetMediaLinkResponse, err error) {
 	var address string
 	if address, err = s.requestAddress(ctx, getMediaLink); err != nil {
 		return
@@ -233,12 +233,12 @@ func (s *MiniDrama) GetMediaLink(ctx context.Context, in *GetMediaLinkRequest) (
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "GetMediaLink")
+	err = util.DecodeWithError(response, &out, "GetMediaLink")
 	return
 }
 
 // DeleteMedia 删除媒体
-func (s *MiniDrama) DeleteMedia(ctx context.Context, in *DeleteMediaRequest) (out *DeleteMediaResponse, err error) {
+func (s *MiniDrama) DeleteMedia(ctx context.Context, in *DeleteMediaRequest) (out DeleteMediaResponse, err error) {
 	var address string
 	if address, err = s.requestAddress(ctx, deleteMedia); err != nil {
 		return
@@ -250,12 +250,12 @@ func (s *MiniDrama) DeleteMedia(ctx context.Context, in *DeleteMediaRequest) (ou
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "DeleteMedia")
+	err = util.DecodeWithError(response, &out, "DeleteMedia")
 	return
 }
 
 // AuditDrama 审核剧本
-func (s *MiniDrama) AuditDrama(ctx context.Context, in *AuditDramaRequest) (out *AuditDramaResponse, err error) {
+func (s *MiniDrama) AuditDrama(ctx context.Context, in *AuditDramaRequest) (out AuditDramaResponse, err error) {
 	var address string
 	if address, err = s.requestAddress(ctx, auditDrama); err != nil {
 		return
@@ -267,12 +267,12 @@ func (s *MiniDrama) AuditDrama(ctx context.Context, in *AuditDramaRequest) (out
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "AuditDrama")
+	err = util.DecodeWithError(response, &out, "AuditDrama")
 	return
 }
 
 // ListDramas 获取剧目列表
-func (s *MiniDrama) ListDramas(ctx context.Context, in *ListDramasRequest) (out *ListDramasResponse, err error) {
+func (s *MiniDrama) ListDramas(ctx context.Context, in *ListDramasRequest) (out ListDramasResponse, err error) {
 	var address string
 	if address, err = s.requestAddress(ctx, listDramas); err != nil {
 		return
@@ -284,12 +284,12 @@ func (s *MiniDrama) ListDramas(ctx context.Context, in *ListDramasRequest) (out
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "ListDramas")
+	err = util.DecodeWithError(response, &out, "ListDramas")
 	return
 }
 
 // GetDrama 获取剧目信息
-func (s *MiniDrama) GetDrama(ctx context.Context, in *GetDramaRequest) (out *GetDramaResponse, err error) {
+func (s *MiniDrama) GetDrama(ctx context.Context, in *GetDramaRequest) (out GetDramaResponse, err error) {
 	var address string
 	if address, err = s.requestAddress(ctx, getDrama); err != nil {
 		return
@@ -300,12 +300,12 @@ func (s *MiniDrama) GetDrama(ctx context.Context, in *GetDramaRequest) (out *Get
 		return
 	}
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "GetDrama")
+	err = util.DecodeWithError(response, &out, "GetDrama")
 	return
 }
 
 // GetCdnUsageData 查询 CDN 用量数据
-func (s *MiniDrama) GetCdnUsageData(ctx context.Context, in *GetCdnUsageDataRequest) (out *GetCdnUsageDataResponse, err error) {
+func (s *MiniDrama) GetCdnUsageData(ctx context.Context, in *GetCdnUsageDataRequest) (out GetCdnUsageDataResponse, err error) {
 	var address string
 	if address, err = s.requestAddress(ctx, getCdnUsageData); err != nil {
 		return
@@ -316,12 +316,12 @@ func (s *MiniDrama) GetCdnUsageData(ctx context.Context, in *GetCdnUsageDataRequ
 		return
 	}
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "GetCdnUsageData")
+	err = util.DecodeWithError(response, &out, "GetCdnUsageData")
 	return
 }
 
 // GetCdnLogs 查询 CDN 日志
-func (s *MiniDrama) GetCdnLogs(ctx context.Context, in *GetCdnLogsRequest) (out *GetCdnLogsResponse, err error) {
+func (s *MiniDrama) GetCdnLogs(ctx context.Context, in *GetCdnLogsRequest) (out GetCdnLogsResponse, err error) {
 	var address string
 	if address, err = s.requestAddress(ctx, getCdnLogs); err != nil {
 		return
@@ -332,7 +332,7 @@ func (s *MiniDrama) GetCdnLogs(ctx context.Context, in *GetCdnLogsRequest) (out
 		return
 	}
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "GetCdnLogs")
+	err = util.DecodeWithError(response, &out, "GetCdnLogs")
 	return
 }
 

+ 28 - 28
miniprogram/virtualpayment/virtualpayment.go

@@ -37,7 +37,7 @@ func (s *VirtualPayment) SetSessionKey(sessionKey string) {
 }
 
 // QueryUserBalance 查询虚拟支付余额
-func (s *VirtualPayment) QueryUserBalance(ctx context.Context, in *QueryUserBalanceRequest) (out *QueryUserBalanceResponse, err error) {
+func (s *VirtualPayment) QueryUserBalance(ctx context.Context, in *QueryUserBalanceRequest) (out QueryUserBalanceResponse, err error) {
 	var jsonByte []byte
 	if jsonByte, err = json.Marshal(in); err != nil {
 		return
@@ -60,12 +60,12 @@ func (s *VirtualPayment) QueryUserBalance(ctx context.Context, in *QueryUserBala
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "QueryUserBalance")
+	err = util.DecodeWithError(response, &out, "QueryUserBalance")
 	return
 }
 
 // CurrencyPay currency pay 扣减代币(一般用于代币支付)
-func (s *VirtualPayment) CurrencyPay(ctx context.Context, in *CurrencyPayRequest) (out *CurrencyPayResponse, err error) {
+func (s *VirtualPayment) CurrencyPay(ctx context.Context, in *CurrencyPayRequest) (out CurrencyPayResponse, err error) {
 	var jsonByte []byte
 	if jsonByte, err = json.Marshal(in); err != nil {
 		return
@@ -88,12 +88,12 @@ func (s *VirtualPayment) CurrencyPay(ctx context.Context, in *CurrencyPayRequest
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "CurrencyPay")
+	err = util.DecodeWithError(response, &out, "CurrencyPay")
 	return
 }
 
 // QueryOrder 查询创建的订单(现金单,非代币单)
-func (s *VirtualPayment) QueryOrder(ctx context.Context, in *QueryOrderRequest) (out *QueryOrderResponse, err error) {
+func (s *VirtualPayment) QueryOrder(ctx context.Context, in *QueryOrderRequest) (out QueryOrderResponse, err error) {
 	var jsonByte []byte
 	if jsonByte, err = json.Marshal(in); err != nil {
 		return
@@ -116,12 +116,12 @@ func (s *VirtualPayment) QueryOrder(ctx context.Context, in *QueryOrderRequest)
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "QueryOrder")
+	err = util.DecodeWithError(response, &out, "QueryOrder")
 	return
 }
 
 // CancelCurrencyPay 取消订单 代币支付退款 (currency_pay 接口的逆操作)
-func (s *VirtualPayment) CancelCurrencyPay(ctx context.Context, in *CancelCurrencyPayRequest) (out *CancelCurrencyPayResponse, err error) {
+func (s *VirtualPayment) CancelCurrencyPay(ctx context.Context, in *CancelCurrencyPayRequest) (out CancelCurrencyPayResponse, err error) {
 	var jsonByte []byte
 	if jsonByte, err = json.Marshal(in); err != nil {
 		return
@@ -144,13 +144,13 @@ func (s *VirtualPayment) CancelCurrencyPay(ctx context.Context, in *CancelCurren
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "CancelCurrencyPay")
+	err = util.DecodeWithError(response, &out, "CancelCurrencyPay")
 	return
 }
 
 // NotifyProvideGoods 通知发货
 // 通知已经发货完成(只能通知现金单),正常通过 xpay_goods_deliver_notify 消息推送返回成功就不需要调用这个 api 接口。这个接口用于异常情况推送不成功时手动将单改成已发货状态
-func (s *VirtualPayment) NotifyProvideGoods(ctx context.Context, in *NotifyProvideGoodsRequest) (out *NotifyProvideGoodsResponse, err error) {
+func (s *VirtualPayment) NotifyProvideGoods(ctx context.Context, in *NotifyProvideGoodsRequest) (out NotifyProvideGoodsResponse, err error) {
 	var jsonByte []byte
 	if jsonByte, err = json.Marshal(in); err != nil {
 		return
@@ -174,12 +174,12 @@ func (s *VirtualPayment) NotifyProvideGoods(ctx context.Context, in *NotifyProvi
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "NotifyProvideGoods")
+	err = util.DecodeWithError(response, &out, "NotifyProvideGoods")
 	return
 }
 
 // PresentCurrency 代币赠送接口,由于目前不支付按单号查赠送单的功能,所以当需要赠送的时候可以一直重试到返回 0 或者返回 268490004(重复操作)为止
-func (s *VirtualPayment) PresentCurrency(ctx context.Context, in *PresentCurrencyRequest) (out *PresentCurrencyResponse, err error) {
+func (s *VirtualPayment) PresentCurrency(ctx context.Context, in *PresentCurrencyRequest) (out PresentCurrencyResponse, err error) {
 	var jsonByte []byte
 	if jsonByte, err = json.Marshal(in); err != nil {
 		return
@@ -203,12 +203,12 @@ func (s *VirtualPayment) PresentCurrency(ctx context.Context, in *PresentCurrenc
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "PresentCurrency")
+	err = util.DecodeWithError(response, &out, "PresentCurrency")
 	return
 }
 
 // DownloadBill 下载订单交易账单
-func (s *VirtualPayment) DownloadBill(ctx context.Context, in *DownloadBillRequest) (out *DownloadBillResponse, err error) {
+func (s *VirtualPayment) DownloadBill(ctx context.Context, in *DownloadBillRequest) (out DownloadBillResponse, err error) {
 	var jsonByte []byte
 	if jsonByte, err = json.Marshal(in); err != nil {
 		return
@@ -232,12 +232,12 @@ func (s *VirtualPayment) DownloadBill(ctx context.Context, in *DownloadBillReque
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "DownloadBill")
+	err = util.DecodeWithError(response, &out, "DownloadBill")
 	return
 }
 
 // RefundOrder 退款 对使用 jsapi 接口下的单进行退款
-func (s *VirtualPayment) RefundOrder(ctx context.Context, in *RefundOrderRequest) (out *RefundOrderResponse, err error) {
+func (s *VirtualPayment) RefundOrder(ctx context.Context, in *RefundOrderRequest) (out RefundOrderResponse, err error) {
 	var jsonByte []byte
 	if jsonByte, err = json.Marshal(in); err != nil {
 		return
@@ -261,12 +261,12 @@ func (s *VirtualPayment) RefundOrder(ctx context.Context, in *RefundOrderRequest
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "RefundOrder")
+	err = util.DecodeWithError(response, &out, "RefundOrder")
 	return
 }
 
 // CreateWithdrawOrder 创建提现单
-func (s *VirtualPayment) CreateWithdrawOrder(ctx context.Context, in *CreateWithdrawOrderRequest) (out *CreateWithdrawOrderResponse, err error) {
+func (s *VirtualPayment) CreateWithdrawOrder(ctx context.Context, in *CreateWithdrawOrderRequest) (out CreateWithdrawOrderResponse, err error) {
 	var jsonByte []byte
 	if jsonByte, err = json.Marshal(in); err != nil {
 		return
@@ -290,12 +290,12 @@ func (s *VirtualPayment) CreateWithdrawOrder(ctx context.Context, in *CreateWith
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "CreateWithdrawOrder")
+	err = util.DecodeWithError(response, &out, "CreateWithdrawOrder")
 	return
 }
 
 // QueryWithdrawOrder 查询提现单
-func (s *VirtualPayment) QueryWithdrawOrder(ctx context.Context, in *QueryWithdrawOrderRequest) (out *QueryWithdrawOrderResponse, err error) {
+func (s *VirtualPayment) QueryWithdrawOrder(ctx context.Context, in *QueryWithdrawOrderRequest) (out QueryWithdrawOrderResponse, err error) {
 	var jsonByte []byte
 	if jsonByte, err = json.Marshal(in); err != nil {
 		return
@@ -319,12 +319,12 @@ func (s *VirtualPayment) QueryWithdrawOrder(ctx context.Context, in *QueryWithdr
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "QueryWithdrawOrder")
+	err = util.DecodeWithError(response, &out, "QueryWithdrawOrder")
 	return
 }
 
 // StartUploadGoods 开始上传商品
-func (s *VirtualPayment) StartUploadGoods(ctx context.Context, in *StartUploadGoodsRequest) (out *StartUploadGoodsResponse, err error) {
+func (s *VirtualPayment) StartUploadGoods(ctx context.Context, in *StartUploadGoodsRequest) (out StartUploadGoodsResponse, err error) {
 	var jsonByte []byte
 	if jsonByte, err = json.Marshal(in); err != nil {
 		return
@@ -348,12 +348,12 @@ func (s *VirtualPayment) StartUploadGoods(ctx context.Context, in *StartUploadGo
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "StartUploadGoods")
+	err = util.DecodeWithError(response, &out, "StartUploadGoods")
 	return
 }
 
 // QueryUploadGoods 查询上传商品
-func (s *VirtualPayment) QueryUploadGoods(ctx context.Context, in *QueryUploadGoodsRequest) (out *QueryUploadGoodsResponse, err error) {
+func (s *VirtualPayment) QueryUploadGoods(ctx context.Context, in *QueryUploadGoodsRequest) (out QueryUploadGoodsResponse, err error) {
 	var jsonByte []byte
 	if jsonByte, err = json.Marshal(in); err != nil {
 		return
@@ -377,12 +377,12 @@ func (s *VirtualPayment) QueryUploadGoods(ctx context.Context, in *QueryUploadGo
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "QueryUploadGoods")
+	err = util.DecodeWithError(response, &out, "QueryUploadGoods")
 	return
 }
 
 // StartPublishGoods 开始发布商品
-func (s *VirtualPayment) StartPublishGoods(ctx context.Context, in *StartPublishGoodsRequest) (out *StartPublishGoodsResponse, err error) {
+func (s *VirtualPayment) StartPublishGoods(ctx context.Context, in *StartPublishGoodsRequest) (out StartPublishGoodsResponse, err error) {
 	var jsonByte []byte
 	if jsonByte, err = json.Marshal(in); err != nil {
 		return
@@ -406,12 +406,12 @@ func (s *VirtualPayment) StartPublishGoods(ctx context.Context, in *StartPublish
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "StartPublishGoods")
+	err = util.DecodeWithError(response, &out, "StartPublishGoods")
 	return
 }
 
 // QueryPublishGoods 查询发布商品
-func (s *VirtualPayment) QueryPublishGoods(ctx context.Context, in *QueryPublishGoodsRequest) (out *QueryPublishGoodsResponse, err error) {
+func (s *VirtualPayment) QueryPublishGoods(ctx context.Context, in *QueryPublishGoodsRequest) (out QueryPublishGoodsResponse, err error) {
 	var jsonByte []byte
 	if jsonByte, err = json.Marshal(in); err != nil {
 		return
@@ -435,7 +435,7 @@ func (s *VirtualPayment) QueryPublishGoods(ctx context.Context, in *QueryPublish
 	}
 
 	// 使用通用方法返回错误
-	err = util.DecodeWithError(response, out, "QueryPublishGoods")
+	err = util.DecodeWithError(response, &out, "QueryPublishGoods")
 	return
 }
 

+ 22 - 22
officialaccount/message/message.go

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

+ 37 - 4
officialaccount/oauth/oauth.go

@@ -1,6 +1,7 @@
 package oauth
 
 import (
+	ctx2 "context"
 	"encoding/json"
 	"fmt"
 	"net/http"
@@ -73,11 +74,28 @@ type ResAccessToken struct {
 	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)
 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)
 	var response []byte
-	response, err = util.HTTPGet(urlStr)
+	response, err = util.HTTPGetContext(ctx, urlStr)
 	if err != nil {
 		return
 	}
@@ -94,9 +112,14 @@ func (oauth *Oauth) GetUserAccessToken(code string) (result ResAccessToken, err
 
 // RefreshAccessToken 刷新access_token
 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)
 	var response []byte
-	response, err = util.HTTPGet(urlStr)
+	response, err = util.HTTPGetContext(ctx, urlStr)
 	if err != nil {
 		return
 	}
@@ -113,9 +136,14 @@ func (oauth *Oauth) RefreshAccessToken(refreshToken string) (result ResAccessTok
 
 // CheckAccessToken 检验access_token是否有效
 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)
 	var response []byte
-	response, err = util.HTTPGet(urlStr)
+	response, err = util.HTTPGetContext(ctx, urlStr)
 	if err != nil {
 		return
 	}
@@ -149,12 +177,17 @@ type UserInfo struct {
 
 // GetUserInfo 如果scope为 snsapi_userinfo 则可以通过此方法获取到用户基本信息
 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 == "" {
 		lang = "zh_CN"
 	}
 	urlStr := fmt.Sprintf(userInfoURL, accessToken, openID, lang)
 	var response []byte
-	response, err = util.HTTPGet(urlStr)
+	response, err = util.HTTPGetContext(ctx, urlStr)
 	if err != nil {
 		return
 	}

+ 3 - 3
officialaccount/server/server.go

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

+ 28 - 0
util/http.go

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

+ 28 - 17
work/addresslist/department.go

@@ -12,7 +12,8 @@ const (
 	// departmentSimpleListURL 获取子部门ID列表
 	departmentSimpleListURL = "https://qyapi.weixin.qq.com/cgi-bin/department/simplelist?access_token=%s&id=%d"
 	// departmentListURL 获取部门列表
-	departmentListURL = "https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token=%s"
+	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=%s&id=%d"
 )
@@ -80,10 +81,8 @@ func (r *Client) DepartmentCreate(req *DepartmentCreateRequest) (*DepartmentCrea
 		return nil, err
 	}
 	result := &DepartmentCreateResponse{}
-	if err = util.DecodeWithError(response, result, "DepartmentCreate"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "DepartmentCreate")
+	return result, err
 }
 
 // DepartmentSimpleList 获取子部门ID列表
@@ -101,30 +100,44 @@ func (r *Client) DepartmentSimpleList(departmentID int) ([]*DepartmentID, error)
 		return nil, err
 	}
 	result := &DepartmentSimpleListResponse{}
-	if err = util.DecodeWithError(response, result, "DepartmentSimpleList"); err != nil {
-		return nil, err
-	}
-	return result.DepartmentID, nil
+	err = util.DecodeWithError(response, result, "DepartmentSimpleList")
+	return result.DepartmentID, err
 }
 
 // DepartmentList 获取部门列表
 // @desc https://developer.work.weixin.qq.com/document/path/90208
 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, err := r.GetAccessToken()
 	if err != nil {
 		return nil, err
 	}
+
+	if departmentID > 0 {
+		formatURL = fmt.Sprintf(departmentListByIDURL, accessToken, departmentID)
+	} else {
+		formatURL = fmt.Sprintf(departmentListURL, accessToken)
+	}
+
 	// 发起http请求
-	response, err := util.HTTPGet(fmt.Sprintf(departmentListURL, accessToken))
+	response, err := util.HTTPGet(formatURL)
 	if err != nil {
 		return nil, err
 	}
 	// 按照结构体解析返回值
 	result := &DepartmentListResponse{}
-	if err = util.DecodeWithError(response, result, "DepartmentList"); err != nil {
-		return nil, err
-	}
+	err = util.DecodeWithError(response, result, "DepartmentList")
 	// 返回数据
 	return result.Department, err
 }
@@ -144,8 +157,6 @@ func (r *Client) DepartmentGet(departmentID int) (*Department, error) {
 		return nil, err
 	}
 	result := &DepartmentGetResponse{}
-	if err = util.DecodeWithError(response, result, "DepartmentGet"); err != nil {
-		return nil, err
-	}
-	return &result.Department, nil
+	err = util.DecodeWithError(response, result, "DepartmentGet")
+	return &result.Department, err
 }

+ 10 - 20
work/addresslist/linkedcorp.go

@@ -41,10 +41,8 @@ func (r *Client) GetPermList() (*GetPermListResponse, error) {
 		return nil, err
 	}
 	result := &GetPermListResponse{}
-	if err = util.DecodeWithError(response, result, "GetPermList"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetPermList")
+	return result, err
 }
 
 // GetLinkedCorpUserRequest 获取互联企业成员详细信息请求
@@ -111,10 +109,8 @@ func (r *Client) GetLinkedCorpUser(req *GetLinkedCorpUserRequest) (*GetLinkedCor
 		return nil, err
 	}
 	result := &GetLinkedCorpUserResponse{}
-	if err = util.DecodeWithError(response, result, "GetLinkedCorpUser"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetLinkedCorpUser")
+	return result, err
 }
 
 // LinkedCorpSimpleListRequest 获取互联企业部门成员请求
@@ -151,10 +147,8 @@ func (r *Client) LinkedCorpSimpleList(req *LinkedCorpSimpleListRequest) (*Linked
 		return nil, err
 	}
 	result := &LinkedCorpSimpleListResponse{}
-	if err = util.DecodeWithError(response, result, "LinkedCorpSimpleList"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "LinkedCorpSimpleList")
+	return result, err
 }
 
 // LinkedCorpUserListRequest 获取互联企业部门成员详情请求
@@ -183,10 +177,8 @@ func (r *Client) LinkedCorpUserList(req *LinkedCorpUserListRequest) (*LinkedCorp
 		return nil, err
 	}
 	result := &LinkedCorpUserListResponse{}
-	if err = util.DecodeWithError(response, result, "LinkedCorpUserList"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "LinkedCorpUserList")
+	return result, err
 }
 
 // LinkedCorpDepartmentListRequest 获取互联企业部门列表请求
@@ -223,8 +215,6 @@ func (r *Client) LinkedCorpDepartmentList(req *LinkedCorpDepartmentListRequest)
 		return nil, err
 	}
 	result := &LinkedCorpDepartmentListResponse{}
-	if err = util.DecodeWithError(response, result, "LinkedCorpDepartmentList"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "LinkedCorpDepartmentList")
+	return result, err
 }

+ 10 - 20
work/addresslist/tag.go

@@ -51,10 +51,8 @@ func (r *Client) CreateTag(req *CreateTagRequest) (*CreateTagResponse, error) {
 		return nil, err
 	}
 	result := &CreateTagResponse{}
-	if err = util.DecodeWithError(response, result, "CreateTag"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "CreateTag")
+	return result, err
 }
 
 type (
@@ -129,10 +127,8 @@ func (r *Client) GetTag(tagID int) (*GetTagResponse, error) {
 		return nil, err
 	}
 	result := &GetTagResponse{}
-	if err = util.DecodeWithError(response, result, "GetTag"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetTag")
+	return result, err
 }
 
 type (
@@ -165,10 +161,8 @@ func (r *Client) AddTagUsers(req *AddTagUsersRequest) (*AddTagUsersResponse, err
 		return nil, err
 	}
 	result := &AddTagUsersResponse{}
-	if err = util.DecodeWithError(response, result, "AddTagUsers"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "AddTagUsers")
+	return result, err
 }
 
 type (
@@ -201,10 +195,8 @@ func (r *Client) DelTagUsers(req *DelTagUsersRequest) (*DelTagUsersResponse, err
 		return nil, err
 	}
 	result := &DelTagUsersResponse{}
-	if err = util.DecodeWithError(response, result, "DelTagUsers"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "DelTagUsers")
+	return result, err
 }
 
 type (
@@ -235,8 +227,6 @@ func (r *Client) ListTag() (*ListTagResponse, error) {
 		return nil, err
 	}
 	result := &ListTagResponse{}
-	if err = util.DecodeWithError(response, result, "ListTag"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "ListTag")
+	return result, err
 }

+ 12 - 28
work/addresslist/user.go

@@ -61,10 +61,7 @@ func (r *Client) UserSimpleList(departmentID int) ([]*UserList, error) {
 	}
 	result := &UserSimpleListResponse{}
 	err = util.DecodeWithError(response, result, "UserSimpleList")
-	if err != nil {
-		return nil, err
-	}
-	return result.UserList, nil
+	return result.UserList, err
 }
 
 type (
@@ -153,10 +150,8 @@ func (r *Client) UserCreate(req *UserCreateRequest) (*UserCreateResponse, error)
 		return nil, err
 	}
 	result := &UserCreateResponse{}
-	if err = util.DecodeWithError(response, result, "UserCreate"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "UserCreate")
+	return result, err
 }
 
 // UserGetResponse 获取部门成员响应
@@ -245,10 +240,7 @@ func (r *Client) UserGet(UserID string) (*UserGetResponse, error) {
 	}
 	result := &UserGetResponse{}
 	err = util.DecodeWithError(response, result, "UserGet")
-	if err != nil {
-		return nil, err
-	}
-	return result, nil
+	return result, err
 }
 
 type (
@@ -279,10 +271,8 @@ func (r *Client) UserDelete(userID string) (*UserDeleteResponse, error) {
 		return nil, err
 	}
 	result := &UserDeleteResponse{}
-	if err = util.DecodeWithError(response, result, "UserDelete"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "UserDelete")
+	return result, err
 }
 
 // UserListIDRequest 获取成员ID列表请求
@@ -324,10 +314,8 @@ func (r *Client) UserListID(req *UserListIDRequest) (*UserListIDResponse, error)
 		return nil, err
 	}
 	result := &UserListIDResponse{}
-	if err = util.DecodeWithError(response, result, "UserListID"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "UserListID")
+	return result, err
 }
 
 type (
@@ -366,10 +354,8 @@ func (r *Client) ConvertToOpenID(userID string) (string, error) {
 		return "", err
 	}
 	result := &convertToOpenIDResponse{}
-	if err = util.DecodeWithError(response, result, "ConvertToOpenID"); err != nil {
-		return "", err
-	}
-	return result.OpenID, nil
+	err = util.DecodeWithError(response, result, "ConvertToOpenID")
+	return result.OpenID, err
 }
 
 type (
@@ -408,8 +394,6 @@ func (r *Client) ConvertToUserID(openID string) (string, error) {
 		return "", err
 	}
 	result := &convertToUserIDResponse{}
-	if err = util.DecodeWithError(response, result, "ConvertToUserID"); err != nil {
-		return "", err
-	}
-	return result.UserID, nil
+	err = util.DecodeWithError(response, result, "ConvertToUserID")
+	return result.UserID, err
 }

+ 2 - 4
work/appchat/appchat.go

@@ -82,11 +82,9 @@ func (r *Client) Send(apiName string, request interface{}) (*SendResponse, error
 	}
 	// 按照结构体解析返回值
 	result := &SendResponse{}
-	if err = util.DecodeWithError(response, result, apiName); err != nil {
-		return nil, err
-	}
+	err = util.DecodeWithError(response, result, apiName)
 	// 返回数据
-	return result, nil
+	return result, err
 }
 
 // SendText 发送文本消息

+ 0 - 69
work/checkin/checkin.go

@@ -1,69 +0,0 @@
-package checkin
-
-import (
-	"fmt"
-
-	"github.com/silenceper/wechat/v2/util"
-)
-
-const (
-	// getCheckinDataURL 获取打卡记录数据
-	getCheckinDataURL = "https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckindata?access_token=%s"
-)
-
-type (
-	// GetCheckinDataRequest 获取打卡记录数据请求
-	GetCheckinDataRequest struct {
-		OpenCheckinDataType int64    `json:"opencheckindatatype"`
-		StartTime           int64    `json:"starttime"`
-		EndTime             int64    `json:"endtime"`
-		UserIDList          []string `json:"useridlist"`
-	}
-	// GetCheckinDataResponse 获取打卡记录数据响应
-	GetCheckinDataResponse struct {
-		util.CommonError
-		CheckinData []*GetCheckinDataItem `json:"checkindata"`
-	}
-	// GetCheckinDataItem 打卡记录数据
-	GetCheckinDataItem struct {
-		UserID         string   `json:"userid"`
-		GroupName      string   `json:"groupname"`
-		CheckinType    string   `json:"checkin_type"`
-		ExceptionType  string   `json:"exception_type"`
-		CheckinTime    int64    `json:"checkin_time"`
-		LocationTitle  string   `json:"location_title"`
-		LocationDetail string   `json:"location_detail"`
-		WifiName       string   `json:"wifiname"`
-		Notes          string   `json:"notes"`
-		WifiMac        string   `json:"wifimac"`
-		MediaIDs       []string `json:"mediaids"`
-		SchCheckinTime int64    `json:"sch_checkin_time"`
-		GroupID        int64    `json:"groupid"`
-		ScheduleID     int64    `json:"schedule_id"`
-		TimelineID     int64    `json:"timeline_id"`
-		Lat            int64    `json:"lat,omitempty"`
-		Lng            int64    `json:"lng,omitempty"`
-		DeviceID       string   `json:"deviceid,omitempty"`
-	}
-)
-
-// GetCheckinData 获取打卡记录数据
-// @see https://developer.work.weixin.qq.com/document/path/90262
-func (r *Client) GetCheckinData(req *GetCheckinDataRequest) (*GetCheckinDataResponse, 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(getCheckinDataURL, accessToken), req); err != nil {
-		return nil, err
-	}
-	result := &GetCheckinDataResponse{}
-	if err = util.DecodeWithError(response, result, "GetCheckinData"); err != nil {
-		return nil, err
-	}
-	return result, nil
-}

+ 660 - 0
work/checkin/record.go

@@ -0,0 +1,660 @@
+package checkin
+
+import (
+	"fmt"
+
+	"github.com/silenceper/wechat/v2/util"
+)
+
+const (
+	// getCheckinDataURL 获取打卡记录数据
+	getCheckinDataURL = "https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckindata?access_token=%s"
+	// getDayDataURL 获取打卡日报数据
+	getDayDataURL = "https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckin_daydata?access_token=%s"
+	// getMonthDataURL 获取打卡月报数据
+	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 (
+	// GetCheckinDataRequest 获取打卡记录数据请求
+	GetCheckinDataRequest struct {
+		OpenCheckinDataType int64    `json:"opencheckindatatype"`
+		StartTime           int64    `json:"starttime"`
+		EndTime             int64    `json:"endtime"`
+		UserIDList          []string `json:"useridlist"`
+	}
+	// GetCheckinDataResponse 获取打卡记录数据响应
+	GetCheckinDataResponse struct {
+		util.CommonError
+		CheckinData []*GetCheckinDataItem `json:"checkindata"`
+	}
+	// GetCheckinDataItem 打卡记录数据
+	GetCheckinDataItem struct {
+		UserID         string   `json:"userid"`
+		GroupName      string   `json:"groupname"`
+		CheckinType    string   `json:"checkin_type"`
+		ExceptionType  string   `json:"exception_type"`
+		CheckinTime    int64    `json:"checkin_time"`
+		LocationTitle  string   `json:"location_title"`
+		LocationDetail string   `json:"location_detail"`
+		WifiName       string   `json:"wifiname"`
+		Notes          string   `json:"notes"`
+		WifiMac        string   `json:"wifimac"`
+		MediaIDs       []string `json:"mediaids"`
+		SchCheckinTime int64    `json:"sch_checkin_time"`
+		GroupID        int64    `json:"groupid"`
+		ScheduleID     int64    `json:"schedule_id"`
+		TimelineID     int64    `json:"timeline_id"`
+		Lat            int64    `json:"lat,omitempty"`
+		Lng            int64    `json:"lng,omitempty"`
+		DeviceID       string   `json:"deviceid,omitempty"`
+	}
+)
+
+// GetCheckinData 获取打卡记录数据
+// @see https://developer.work.weixin.qq.com/document/path/90262
+func (r *Client) GetCheckinData(req *GetCheckinDataRequest) (*GetCheckinDataResponse, 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(getCheckinDataURL, accessToken), req); err != nil {
+		return nil, err
+	}
+	result := &GetCheckinDataResponse{}
+	err = util.DecodeWithError(response, result, "GetCheckinData")
+	return result, err
+}
+
+type (
+	// GetDayDataResponse 获取打卡日报数据
+	GetDayDataResponse struct {
+		util.CommonError
+		Datas []DayDataItem `json:"datas"`
+	}
+
+	// DayDataItem 日报
+	DayDataItem struct {
+		BaseInfo       DayBaseInfo     `json:"base_info"`
+		SummaryInfo    DaySummaryInfo  `json:"summary_info"`
+		HolidayInfos   []HolidayInfo   `json:"holiday_infos"`
+		ExceptionInfos []ExceptionInfo `json:"exception_infos"`
+		OtInfo         OtInfo          `json:"ot_info"`
+		SpItems        []SpItem        `json:"sp_items"`
+	}
+
+	// DayBaseInfo 基础信息
+	DayBaseInfo struct {
+		Date        int64       `json:"date"`
+		RecordType  int64       `json:"record_type"`
+		Name        string      `json:"name"`
+		NameEx      string      `json:"name_ex"`
+		DepartsName string      `json:"departs_name"`
+		AcctID      string      `json:"acctid"`
+		DayType     int64       `json:"day_type"`
+		RuleInfo    DayRuleInfo `json:"rule_info"`
+	}
+
+	// DayCheckInTime 当日打卡时间
+	DayCheckInTime struct {
+		WorkSec    int64 `json:"work_sec"`
+		OffWorkSec int64 `json:"off_work_sec"`
+	}
+
+	// DayRuleInfo 打卡人员所属规则信息
+	DayRuleInfo struct {
+		GroupID      int64            `json:"groupid"`
+		GroupName    string           `json:"groupname"`
+		ScheduleID   int64            `json:"scheduleid"`
+		ScheduleName string           `json:"schedulename"`
+		CheckInTimes []DayCheckInTime `json:"checkintime"`
+	}
+
+	// DaySummaryInfo 汇总信息
+	DaySummaryInfo struct {
+		CheckinCount    int64 `json:"checkin_count"`
+		RegularWorkSec  int64 `json:"regular_work_sec"`
+		StandardWorkSec int64 `json:"standard_work_sec"`
+		EarliestTime    int64 `json:"earliest_time"`
+		LastestTime     int64 `json:"lastest_time"`
+	}
+
+	// HolidayInfo 假勤相关信息
+	HolidayInfo struct {
+		SpNumber      string        `json:"sp_number"`
+		SpTitle       SpTitle       `json:"sp_title"`
+		SpDescription SpDescription `json:"sp_description"`
+	}
+
+	// SpTitle 假勤信息摘要-标题信息
+	SpTitle struct {
+		Data []SpData `json:"data"`
+	}
+
+	// SpDescription 假勤信息摘要-描述信息
+	SpDescription struct {
+		Data []SpData `json:"data"`
+	}
+
+	// SpData 假勤信息(多种语言描述,目前只有中文一种)
+	SpData struct {
+		Lang string `json:"lang"`
+		Text string `json:"text"`
+	}
+
+	// SpItem 假勤统计信息
+	SpItem struct {
+		Count      int64  `json:"count"`
+		Duration   int64  `json:"duration"`
+		TimeType   int64  `json:"time_type"`
+		Type       int64  `json:"type"`
+		VacationID int64  `json:"vacation_id"`
+		Name       string `json:"name"`
+	}
+
+	// ExceptionInfo 校准状态信息
+	ExceptionInfo struct {
+		Count     int64 `json:"count"`
+		Duration  int64 `json:"duration"`
+		Exception int64 `json:"exception"`
+	}
+
+	// OtInfo 加班信息
+	OtInfo struct {
+		OtStatus          int64    `json:"ot_status"`
+		OtDuration        int64    `json:"ot_duration"`
+		ExceptionDuration []uint64 `json:"exception_duration"`
+	}
+)
+
+// GetDayData 获取打卡日报数据
+// @see https://developer.work.weixin.qq.com/document/path/96498
+func (r *Client) GetDayData(req *GetCheckinDataRequest) (result *GetDayDataResponse, err error) {
+	var (
+		response    []byte
+		accessToken string
+	)
+	if accessToken, err = r.GetAccessToken(); err != nil {
+		return
+	}
+	if response, err = util.PostJSON(fmt.Sprintf(getDayDataURL, accessToken), req); err != nil {
+		return
+	}
+
+	result = new(GetDayDataResponse)
+	err = util.DecodeWithError(response, result, "GetDayData")
+	return
+}
+
+type (
+	// GetMonthDataResponse 获取打卡月报数据
+	GetMonthDataResponse struct {
+		util.CommonError
+		Datas []MonthDataItem `json:"datas"`
+	}
+
+	// MonthDataItem 月报数据
+	MonthDataItem struct {
+		BaseInfo       MonthBaseInfo    `json:"base_info"`
+		SummaryInfo    MonthSummaryInfo `json:"summary_info"`
+		ExceptionInfos []ExceptionInfo  `json:"exception_infos"`
+		SpItems        []SpItem         `json:"sp_items"`
+		OverWorkInfo   OverWorkInfo     `json:"overwork_info"`
+	}
+
+	// MonthBaseInfo 基础信息
+	MonthBaseInfo struct {
+		RecordType  int64         `json:"record_type"`
+		Name        string        `json:"name"`
+		NameEx      string        `json:"name_ex"`
+		DepartsName string        `json:"departs_name"`
+		AcctID      string        `json:"acctid"`
+		RuleInfo    MonthRuleInfo `json:"rule_info"`
+	}
+
+	// MonthRuleInfo 打卡人员所属规则信息
+	MonthRuleInfo struct {
+		GroupID   int64  `json:"groupid"`
+		GroupName string `json:"groupname"`
+	}
+
+	// MonthSummaryInfo 汇总信息
+	MonthSummaryInfo struct {
+		WorkDays        int64 `json:"work_days"`
+		ExceptDays      int64 `json:"except_days"`
+		RegularDays     int64 `json:"regular_days"`
+		RegularWorkSec  int64 `json:"regular_work_sec"`
+		StandardWorkSec int64 `json:"standard_work_sec"`
+	}
+
+	// OverWorkInfo 加班情况
+	OverWorkInfo struct {
+		WorkdayOverSec int64 `json:"workday_over_sec"`
+		HolidayOverSec int64 `json:"holidays_over_sec"`
+		RestDayOverSec int64 `json:"restdays_over_sec"`
+	}
+)
+
+// GetMonthData 获取打卡月报数据
+// @see https://developer.work.weixin.qq.com/document/path/96499
+func (r *Client) GetMonthData(req *GetCheckinDataRequest) (result *GetMonthDataResponse, err error) {
+	var (
+		response    []byte
+		accessToken string
+	)
+	if accessToken, err = r.GetAccessToken(); err != nil {
+		return
+	}
+	if response, err = util.PostJSON(fmt.Sprintf(getMonthDataURL, accessToken), req); err != nil {
+		return
+	}
+
+	result = new(GetMonthDataResponse)
+	err = util.DecodeWithError(response, result, "GetMonthData")
+	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
+}

+ 1 - 3
work/externalcontact/callback.go

@@ -38,8 +38,6 @@ func (r *Client) GetCallbackMessage(encryptedMsg []byte) (msg EventCallbackMessa
 	if err != nil {
 		return
 	}
-	if err = xml.Unmarshal(bData, &msg); err != nil {
-		return
-	}
+	err = xml.Unmarshal(bData, &msg)
 	return
 }

+ 10 - 20
work/externalcontact/contact_way.go

@@ -102,10 +102,8 @@ func (r *Client) AddContactWay(req *AddContactWayRequest) (*AddContactWayRespons
 		return nil, err
 	}
 	result := &AddContactWayResponse{}
-	if err = util.DecodeWithError(response, result, "AddContactWay"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "AddContactWay")
+	return result, err
 }
 
 type (
@@ -153,10 +151,8 @@ func (r *Client) GetContactWay(req *GetContactWayRequest) (*GetContactWayRespons
 		return nil, err
 	}
 	result := &GetContactWayResponse{}
-	if err = util.DecodeWithError(response, result, "GetContactWay"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetContactWay")
+	return result, err
 }
 
 type (
@@ -195,10 +191,8 @@ func (r *Client) UpdateContactWay(req *UpdateContactWayRequest) (*UpdateContactW
 		return nil, err
 	}
 	result := &UpdateContactWayResponse{}
-	if err = util.DecodeWithError(response, result, "UpdateContactWay"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "UpdateContactWay")
+	return result, err
 }
 
 type (
@@ -236,10 +230,8 @@ func (r *Client) ListContactWay(req *ListContactWayRequest) (*ListContactWayResp
 		return nil, err
 	}
 	result := &ListContactWayResponse{}
-	if err = util.DecodeWithError(response, result, "ListContactWay"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "ListContactWay")
+	return result, err
 }
 
 type (
@@ -268,8 +260,6 @@ func (r *Client) DelContactWay(req *DelContactWayRequest) (*DelContactWayRespons
 		return nil, err
 	}
 	result := &DelContactWayResponse{}
-	if err = util.DecodeWithError(response, result, "DelContactWay"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "DelContactWay")
+	return result, err
 }

+ 17 - 33
work/externalcontact/customer_acquisition.go

@@ -54,10 +54,8 @@ func (r *Client) ListLink(req *ListLinkRequest) (*ListLinkResponse, error) {
 		return nil, err
 	}
 	result := &ListLinkResponse{}
-	if err = util.DecodeWithError(response, result, "ListLink"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "ListLink")
+	return result, err
 }
 
 type (
@@ -102,10 +100,8 @@ func (r *Client) GetCustomerAcquisition(req *GetCustomerAcquisitionRequest) (*Ge
 		return nil, err
 	}
 	result := &GetCustomerAcquisitionResponse{}
-	if err = util.DecodeWithError(response, result, "GetCustomerAcquisition"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetCustomerAcquisition")
+	return result, err
 }
 
 type (
@@ -137,10 +133,8 @@ func (r *Client) CreateCustomerAcquisitionLink(req *CreateCustomerAcquisitionLin
 		return nil, err
 	}
 	result := &CreateCustomerAcquisitionLinkResponse{}
-	if err = util.DecodeWithError(response, result, "CreateCustomerAcquisitionLink"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "CreateCustomerAcquisitionLink")
+	return result, err
 }
 
 type (
@@ -172,10 +166,8 @@ func (r *Client) UpdateCustomerAcquisitionLink(req *UpdateCustomerAcquisitionLin
 		return nil, err
 	}
 	result := &UpdateCustomerAcquisitionLinkResponse{}
-	if err = util.DecodeWithError(response, result, "UpdateCustomerAcquisitionLink"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "UpdateCustomerAcquisitionLink")
+	return result, err
 }
 
 type (
@@ -204,10 +196,8 @@ func (r *Client) DeleteCustomerAcquisitionLink(req *DeleteCustomerAcquisitionLin
 		return nil, err
 	}
 	result := &DeleteCustomerAcquisitionLinkResponse{}
-	if err = util.DecodeWithError(response, result, "DeleteCustomerAcquisitionLink"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "DeleteCustomerAcquisitionLink")
+	return result, err
 }
 
 type (
@@ -247,10 +237,8 @@ func (r *Client) GetCustomerInfoWithCustomerAcquisitionLink(req *GetCustomerInfo
 		return nil, err
 	}
 	result := &GetCustomerInfoWithCustomerAcquisitionLinkResponse{}
-	if err = util.DecodeWithError(response, result, "GetCustomerInfoWithCustomerAcquisitionLink"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetCustomerInfoWithCustomerAcquisitionLink")
+	return result, err
 }
 
 type (
@@ -279,14 +267,12 @@ func (r *Client) CustomerAcquisitionQuota() (*CustomerAcquisitionQuotaResponse,
 		return nil, err
 	}
 	var response []byte
-	if response, err = util.HTTPGet((fmt.Sprintf(customerAcquisitionQuotaURL, accessToken))); err != nil {
+	if response, err = util.HTTPGet(fmt.Sprintf(customerAcquisitionQuotaURL, accessToken)); err != nil {
 		return nil, err
 	}
 	result := &CustomerAcquisitionQuotaResponse{}
-	if err = util.DecodeWithError(response, result, "CustomerAcquisitionQuota"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "CustomerAcquisitionQuota")
+	return result, err
 }
 
 type (
@@ -319,8 +305,6 @@ func (r *Client) CustomerAcquisitionStatistic(req *CustomerAcquisitionStatisticR
 		return nil, err
 	}
 	result := &CustomerAcquisitionStatisticResponse{}
-	if err = util.DecodeWithError(response, result, "CustomerAcquisitionStatistic"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "CustomerAcquisitionStatistic")
+	return result, err
 }

+ 12 - 29
work/externalcontact/external_user.go

@@ -50,10 +50,7 @@ func (r *Client) GetExternalUserList(userID string) ([]string, error) {
 	}
 	var result ExternalUserListResponse
 	err = util.DecodeWithError(response, &result, "GetExternalUserList")
-	if err != nil {
-		return nil, err
-	}
-	return result.ExternalUserID, nil
+	return result.ExternalUserID, err
 }
 
 // ExternalUserDetailResponse 外部联系人详情响应
@@ -104,7 +101,7 @@ type Tag struct {
 // WechatChannel 视频号添加的场景
 type WechatChannel struct {
 	NickName string `json:"nickname"`
-	Source   string `json:"source"`
+	Source   int    `json:"source"`
 }
 
 // GetExternalUserDetail 获取外部联系人详情
@@ -125,10 +122,7 @@ func (r *Client) GetExternalUserDetail(externalUserID string, nextCursor ...stri
 	}
 	result := &ExternalUserDetailResponse{}
 	err = util.DecodeWithError(response, result, "get_external_user_detail")
-	if err != nil {
-		return nil, err
-	}
-	return result, nil
+	return result, err
 }
 
 // BatchGetExternalUserDetailsRequest 批量获取外部联系人详情请求
@@ -196,10 +190,7 @@ func (r *Client) BatchGetExternalUserDetails(request BatchGetExternalUserDetails
 	}
 	var result ExternalUserDetailListResponse
 	err = util.DecodeWithError(response, &result, "BatchGetExternalUserDetails")
-	if err != nil {
-		return nil, err
-	}
-	return result.ExternalContactList, nil
+	return result.ExternalContactList, err
 }
 
 // UpdateUserRemarkRequest 修改客户备注信息请求体
@@ -265,10 +256,8 @@ func (r *Client) ListCustomerStrategy(req *ListCustomerStrategyRequest) (*ListCu
 		return nil, err
 	}
 	result := &ListCustomerStrategyResponse{}
-	if err = util.DecodeWithError(response, result, "ListCustomerStrategy"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "ListCustomerStrategy")
+	return result, err
 }
 
 // GetCustomerStrategyRequest 获取规则组详情请求
@@ -332,10 +321,8 @@ func (r *Client) GetCustomerStrategy(req *GetCustomerStrategyRequest) (*GetCusto
 		return nil, err
 	}
 	result := &GetCustomerStrategyResponse{}
-	if err = util.DecodeWithError(response, result, "GetCustomerStrategy"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetCustomerStrategy")
+	return result, err
 }
 
 // GetRangeCustomerStrategyRequest 获取规则组管理范围请求
@@ -374,10 +361,8 @@ func (r *Client) GetRangeCustomerStrategy(req *GetRangeCustomerStrategyRequest)
 		return nil, err
 	}
 	result := &GetRangeCustomerStrategyResponse{}
-	if err = util.DecodeWithError(response, result, "GetRangeCustomerStrategy"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetRangeCustomerStrategy")
+	return result, err
 }
 
 // CreateCustomerStrategyRequest 创建新的规则组请求
@@ -410,10 +395,8 @@ func (r *Client) CreateCustomerStrategy(req *CreateCustomerStrategyRequest) (*Cr
 		return nil, err
 	}
 	result := &CreateCustomerStrategyResponse{}
-	if err = util.DecodeWithError(response, result, "CreateCustomerStrategy"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "CreateCustomerStrategy")
+	return result, err
 }
 
 // EditCustomerStrategyRequest 编辑规则组及其管理范围请求

+ 1 - 4
work/externalcontact/follow_user.go

@@ -31,8 +31,5 @@ func (r *Client) GetFollowUserList() ([]string, error) {
 	}
 	var result followerUserResponse
 	err = util.DecodeWithError(response, &result, "GetFollowUserList")
-	if err != nil {
-		return nil, err
-	}
-	return result.FollowUser, nil
+	return result.FollowUser, err
 }

+ 6 - 12
work/externalcontact/groupchat.go

@@ -44,10 +44,8 @@ func (r *Client) GetGroupChatList(req *GroupChatListRequest) (*GroupChatListResp
 		return nil, err
 	}
 	result := &GroupChatListResponse{}
-	if err = util.DecodeWithError(response, result, "GetGroupChatList"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetGroupChatList")
+	return result, err
 }
 
 type (
@@ -106,10 +104,8 @@ func (r *Client) GetGroupChatDetail(req *GroupChatDetailRequest) (*GroupChatDeta
 		return nil, err
 	}
 	result := &GroupChatDetailResponse{}
-	if err = util.DecodeWithError(response, result, "GetGroupChatDetail"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetGroupChatDetail")
+	return result, err
 }
 
 type (
@@ -137,8 +133,6 @@ func (r *Client) OpengIDToChatID(req *OpengIDToChatIDRequest) (*OpengIDToChatIDR
 		return nil, err
 	}
 	result := &OpengIDToChatIDResponse{}
-	if err = util.DecodeWithError(response, result, "GetGroupChatDetail"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetGroupChatDetail")
+	return result, err
 }

+ 4 - 8
work/externalcontact/join_way.go

@@ -44,10 +44,8 @@ func (r *Client) AddJoinWay(req *AddJoinWayRequest) (*AddJoinWayResponse, error)
 		return nil, err
 	}
 	result := &AddJoinWayResponse{}
-	if err = util.DecodeWithError(response, result, "AddJoinWay"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "AddJoinWay")
+	return result, err
 }
 
 type (
@@ -91,10 +89,8 @@ func (r *Client) GetJoinWay(req *JoinWayConfigRequest) (*GetJoinWayResponse, err
 		return nil, err
 	}
 	result := &GetJoinWayResponse{}
-	if err = util.DecodeWithError(response, result, "GetJoinWay"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetJoinWay")
+	return result, err
 }
 
 // UpdateJoinWayRequest 更新群配置的请求参数

+ 22 - 44
work/externalcontact/moment.go

@@ -112,10 +112,8 @@ func (r *Client) AddMomentTask(req *AddMomentTaskRequest) (*AddMomentTaskRespons
 		return nil, err
 	}
 	result := &AddMomentTaskResponse{}
-	if err = util.DecodeWithError(response, result, "AddMomentTask"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "AddMomentTask")
+	return result, err
 }
 
 // GetMomentTaskResultResponse 获取任务创建结果响应
@@ -161,10 +159,8 @@ func (r *Client) GetMomentTaskResult(jobID string) (*GetMomentTaskResultResponse
 		return nil, err
 	}
 	result := &GetMomentTaskResultResponse{}
-	if err = util.DecodeWithError(response, result, "GetMomentTaskResult"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetMomentTaskResult")
+	return result, err
 }
 
 // CancelMomentTaskRequest 停止发表企业朋友圈请求
@@ -264,10 +260,8 @@ func (r *Client) GetMomentList(req *GetMomentListRequest) (*GetMomentListRespons
 		return nil, err
 	}
 	result := &GetMomentListResponse{}
-	if err = util.DecodeWithError(response, result, "GetMomentList"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetMomentList")
+	return result, err
 }
 
 // GetMomentTaskRequest 获取客户朋友圈企业发表的列表请求
@@ -305,10 +299,8 @@ func (r *Client) GetMomentTask(req *GetMomentTaskRequest) (*GetMomentTaskRespons
 		return nil, err
 	}
 	result := &GetMomentTaskResponse{}
-	if err = util.DecodeWithError(response, result, "GetMomentTask"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetMomentTask")
+	return result, err
 }
 
 // GetMomentCustomerListRequest 获取客户朋友圈发表时选择的可见范围请求
@@ -347,10 +339,8 @@ func (r *Client) GetMomentCustomerList(req *GetMomentCustomerListRequest) (*GetM
 		return nil, err
 	}
 	result := &GetMomentCustomerListResponse{}
-	if err = util.DecodeWithError(response, result, "GetMomentCustomerList"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetMomentCustomerList")
+	return result, err
 }
 
 // GetMomentSendResultRequest 获取客户朋友圈发表后的可见客户列表请求
@@ -388,10 +378,8 @@ func (r *Client) GetMomentSendResult(req *GetMomentSendResultRequest) (*GetMomen
 		return nil, err
 	}
 	result := &GetMomentSendResultResponse{}
-	if err = util.DecodeWithError(response, result, "GetMomentSendResult"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetMomentSendResult")
+	return result, err
 }
 
 // GetMomentCommentsRequest 获取客户朋友圈的互动数据请求
@@ -436,10 +424,8 @@ func (r *Client) GetMomentComments(req *GetMomentCommentsRequest) (*GetMomentCom
 		return nil, err
 	}
 	result := &GetMomentCommentsResponse{}
-	if err = util.DecodeWithError(response, result, "GetMomentComments"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetMomentComments")
+	return result, err
 }
 
 // ListMomentStrategyRequest 获取规则组列表请求
@@ -475,10 +461,8 @@ func (r *Client) ListMomentStrategy(req *ListMomentStrategyRequest) (*ListMoment
 		return nil, err
 	}
 	result := &ListMomentStrategyResponse{}
-	if err = util.DecodeWithError(response, result, "ListMomentStrategy"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "ListMomentStrategy")
+	return result, err
 }
 
 // GetMomentStrategyRequest 获取规则组详情请求
@@ -524,10 +508,8 @@ func (r *Client) GetMomentStrategy(req *GetMomentStrategyRequest) (*GetMomentStr
 		return nil, err
 	}
 	result := &GetMomentStrategyResponse{}
-	if err = util.DecodeWithError(response, result, "GetMomentStrategy"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetMomentStrategy")
+	return result, err
 }
 
 // GetRangeMomentStrategyRequest 获取规则组管理范围请求
@@ -566,10 +548,8 @@ func (r *Client) GetRangeMomentStrategy(req *GetRangeMomentStrategyRequest) (*Ge
 		return nil, err
 	}
 	result := &GetRangeMomentStrategyResponse{}
-	if err = util.DecodeWithError(response, result, "GetRangeMomentStrategy"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetRangeMomentStrategy")
+	return result, err
 }
 
 // CreateMomentStrategyRequest 创建新的规则组请求
@@ -602,10 +582,8 @@ func (r *Client) CreateMomentStrategy(req *CreateMomentStrategyRequest) (*Create
 		return nil, err
 	}
 	result := &CreateMomentStrategyResponse{}
-	if err = util.DecodeWithError(response, result, "CreateMomentStrategy"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "CreateMomentStrategy")
+	return result, err
 }
 
 // EditMomentStrategyRequest 编辑规则组及其管理范围请求

+ 51 - 57
work/externalcontact/msg.go

@@ -38,8 +38,23 @@ type AddMsgTemplateRequest struct {
 	Sender         string        `json:"sender,omitempty"`
 	Text           MsgText       `json:"text"`
 	Attachments    []*Attachment `json:"attachments"`
+	AllowSelect    bool          `json:"allow_select,omitempty"`
+	ChatIDList     []string      `json:"chat_id_list,omitempty"`
+	TagFilter      TagFilter     `json:"tag_filter,omitempty"`
 }
 
+type (
+	// TagFilter 标签过滤
+	TagFilter struct {
+		GroupList []TagGroupList `json:"group_list"`
+	}
+
+	// TagGroupList 标签组
+	TagGroupList struct {
+		TagList []string `json:"tag_list"`
+	}
+)
+
 // MsgText 文本消息
 type MsgText struct {
 	Content string `json:"content"`
@@ -106,10 +121,8 @@ func (r *Client) AddMsgTemplate(req *AddMsgTemplateRequest) (*AddMsgTemplateResp
 		return nil, err
 	}
 	result := &AddMsgTemplateResponse{}
-	if err = util.DecodeWithError(response, result, "AddMsgTemplate"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "AddMsgTemplate")
+	return result, err
 }
 
 // GetGroupMsgListV2Request 获取群发记录列表请求
@@ -155,10 +168,8 @@ func (r *Client) GetGroupMsgListV2(req *GetGroupMsgListV2Request) (*GetGroupMsgL
 		return nil, err
 	}
 	result := &GetGroupMsgListV2Response{}
-	if err = util.DecodeWithError(response, result, "GetGroupMsgListV2"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetGroupMsgListV2")
+	return result, err
 }
 
 // GetGroupMsgTaskRequest 获取群发成员发送任务列表请求
@@ -197,10 +208,8 @@ func (r *Client) GetGroupMsgTask(req *GetGroupMsgTaskRequest) (*GetGroupMsgTaskR
 		return nil, err
 	}
 	result := &GetGroupMsgTaskResponse{}
-	if err = util.DecodeWithError(response, result, "GetGroupMsgTask"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetGroupMsgTask")
+	return result, err
 }
 
 // GetGroupMsgSendResultRequest 获取企业群发成员执行结果请求
@@ -242,10 +251,8 @@ func (r *Client) GetGroupMsgSendResult(req *GetGroupMsgSendResultRequest) (*GetG
 		return nil, err
 	}
 	result := &GetGroupMsgSendResultResponse{}
-	if err = util.DecodeWithError(response, result, "GetGroupMsgSendResult"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetGroupMsgSendResult")
+	return result, err
 }
 
 // SendWelcomeMsgRequest 发送新客户欢迎语请求
@@ -275,22 +282,19 @@ func (r *Client) SendWelcomeMsg(req *SendWelcomeMsgRequest) error {
 		return err
 	}
 	result := &SendWelcomeMsgResponse{}
-	if err = util.DecodeWithError(response, result, "SendWelcomeMsg"); err != nil {
-		return err
-	}
-	return nil
+	return util.DecodeWithError(response, result, "SendWelcomeMsg")
 }
 
 // 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"`
+	Text        MsgText                `json:"text"`
+	Image       *AttachmentImg         `json:"image,omitempty"`
+	Link        *AttachmentLink        `json:"link,omitempty"`
+	MiniProgram *AttachmentMiniProgram `json:"miniprogram,omitempty"`
+	File        *AttachmentFile        `json:"file,omitempty"`
+	Video       *AttachmentVideo       `json:"video,omitempty"`
+	AgentID     int                    `json:"agentid,omitempty"`
+	Notify      int                    `json:"notify,omitempty"`
 }
 
 // AddGroupWelcomeTemplateResponse 添加入群欢迎语素材响应
@@ -314,22 +318,20 @@ func (r *Client) AddGroupWelcomeTemplate(req *AddGroupWelcomeTemplateRequest) (*
 		return nil, err
 	}
 	result := &AddGroupWelcomeTemplateResponse{}
-	if err = util.DecodeWithError(response, result, "AddGroupWelcomeTemplate"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "AddGroupWelcomeTemplate")
+	return result, err
 }
 
 // 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"`
+	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 编辑入群欢迎语素材响应
@@ -352,10 +354,7 @@ func (r *Client) EditGroupWelcomeTemplate(req *EditGroupWelcomeTemplateRequest)
 		return err
 	}
 	result := &EditGroupWelcomeTemplateResponse{}
-	if err = util.DecodeWithError(response, result, "EditGroupWelcomeTemplate"); err != nil {
-		return err
-	}
-	return nil
+	return util.DecodeWithError(response, result, "EditGroupWelcomeTemplate")
 }
 
 // GetGroupWelcomeTemplateRequest 获取入群欢迎语素材请求
@@ -367,11 +366,11 @@ type GetGroupWelcomeTemplateRequest struct {
 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"`
+	Image       AttachmentImg         `json:"image,omitempty"`
+	Link        AttachmentLink        `json:"link,omitempty"`
+	MiniProgram AttachmentMiniProgram `json:"miniprogram,omitempty"`
+	File        AttachmentFile        `json:"file,omitempty"`
+	Video       AttachmentVideo       `json:"video,omitempty"`
 }
 
 // GetGroupWelcomeTemplate 获取入群欢迎语素材
@@ -389,10 +388,8 @@ func (r *Client) GetGroupWelcomeTemplate(req *GetGroupWelcomeTemplateRequest) (*
 		return nil, err
 	}
 	result := &GetGroupWelcomeTemplateResponse{}
-	if err = util.DecodeWithError(response, result, "GetGroupWelcomeTemplate"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetGroupWelcomeTemplate")
+	return result, err
 }
 
 // DelGroupWelcomeTemplateRequest 删除入群欢迎语素材请求
@@ -421,10 +418,7 @@ func (r *Client) DelGroupWelcomeTemplate(req *DelGroupWelcomeTemplateRequest) er
 		return err
 	}
 	result := &DelGroupWelcomeTemplateResponse{}
-	if err = util.DecodeWithError(response, result, "DelGroupWelcomeTemplate"); err != nil {
-		return err
-	}
-	return nil
+	return util.DecodeWithError(response, result, "DelGroupWelcomeTemplate")
 }
 
 // RemindGroupMsgSendRequest 提醒成员群发请求

+ 3 - 12
work/externalcontact/statistic.go

@@ -60,10 +60,7 @@ func (r *Client) GetUserBehaviorData(req *GetUserBehaviorRequest) ([]BehaviorDat
 	}
 	var result GetUserBehaviorResponse
 	err = util.DecodeWithError(response, &result, "GetUserBehaviorData")
-	if err != nil {
-		return nil, err
-	}
-	return result.BehaviorData, nil
+	return result.BehaviorData, err
 }
 
 type (
@@ -126,10 +123,7 @@ func (r *Client) GetGroupChatStat(req *GetGroupChatStatRequest) (*GetGroupChatSt
 	}
 	result := &GetGroupChatStatResponse{}
 	err = util.DecodeWithError(response, result, "GetGroupChatStat")
-	if err != nil {
-		return nil, err
-	}
-	return result, nil
+	return result, err
 }
 
 type (
@@ -169,8 +163,5 @@ func (r *Client) GetGroupChatStatByDay(req *GetGroupChatStatByDayRequest) ([]Get
 	}
 	var result GetGroupChatStatByDayResponse
 	err = util.DecodeWithError(response, &result, "GetGroupChatStatByDay")
-	if err != nil {
-		return nil, err
-	}
-	return result.Items, nil
+	return result.Items, err
 }

+ 6 - 16
work/externalcontact/tag.go

@@ -77,10 +77,7 @@ func (r *Client) GetCropTagList(req GetCropTagRequest) ([]TagGroup, error) {
 	}
 	var result GetCropTagListResponse
 	err = util.DecodeWithError(response, &result, "GetCropTagList")
-	if err != nil {
-		return nil, err
-	}
-	return result.TagGroup, nil
+	return result.TagGroup, err
 }
 
 // AddCropTagRequest 添加企业标签请求
@@ -123,10 +120,7 @@ func (r *Client) AddCropTag(req AddCropTagRequest) (*TagGroup, error) {
 	}
 	var result AddCropTagResponse
 	err = util.DecodeWithError(response, &result, "AddCropTag")
-	if err != nil {
-		return nil, err
-	}
-	return &result.TagGroup, nil
+	return &result.TagGroup, err
 }
 
 // EditCropTagRequest 编辑客户企业标签请求
@@ -256,10 +250,8 @@ func (r *Client) GetStrategyTagList(req *GetStrategyTagListRequest) (*GetStrateg
 		return nil, err
 	}
 	result := &GetStrategyTagListResponse{}
-	if err = util.DecodeWithError(response, result, "GetStrategyTagList"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetStrategyTagList")
+	return result, err
 }
 
 // AddStrategyTagRequest 为指定规则组创建企业客户标签请求
@@ -315,10 +307,8 @@ func (r *Client) AddStrategyTag(req *AddStrategyTagRequest) (*AddStrategyTagResp
 		return nil, err
 	}
 	result := &AddStrategyTagResponse{}
-	if err = util.DecodeWithError(response, result, "AddStrategyTag"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "AddStrategyTag")
+	return result, err
 }
 
 // EditStrategyTagRequest 编辑指定规则组下的企业客户标签请求

+ 14 - 28
work/externalcontact/transfer.go

@@ -58,10 +58,8 @@ func (r *Client) TransferCustomer(req *TransferCustomerRequest) (*TransferCustom
 		return nil, err
 	}
 	result := &TransferCustomerResponse{}
-	if err = util.DecodeWithError(response, result, "TransferCustomer"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "TransferCustomer")
+	return result, err
 }
 
 // TransferResultRequest 查询客户接替状态请求
@@ -100,10 +98,8 @@ func (r *Client) TransferResult(req *TransferResultRequest) (*TransferResultResp
 		return nil, err
 	}
 	result := &TransferResultResponse{}
-	if err = util.DecodeWithError(response, result, "TransferResult"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "TransferResult")
+	return result, err
 }
 
 // GroupChatOnJobTransferRequest 分配在职成员的客户群请求
@@ -140,10 +136,8 @@ func (r *Client) GroupChatOnJobTransfer(req *GroupChatOnJobTransferRequest) (*Gr
 		return nil, err
 	}
 	result := &GroupChatOnJobTransferResponse{}
-	if err = util.DecodeWithError(response, result, "GroupChatOnJobTransfer"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GroupChatOnJobTransfer")
+	return result, err
 }
 
 // GetUnassignedListRequest 获取待分配的离职成员列表请求
@@ -182,10 +176,8 @@ func (r *Client) GetUnassignedList(req *GetUnassignedListRequest) (*GetUnassigne
 		return nil, err
 	}
 	result := &GetUnassignedListResponse{}
-	if err = util.DecodeWithError(response, result, "GetUnassignedList"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetUnassignedList")
+	return result, err
 }
 
 // ResignedTransferCustomerRequest 分配离职成员的客户请求
@@ -216,10 +208,8 @@ func (r *Client) ResignedTransferCustomer(req *ResignedTransferCustomerRequest)
 		return nil, err
 	}
 	result := &ResignedTransferCustomerResponse{}
-	if err = util.DecodeWithError(response, result, "ResignedTransferCustomer"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "ResignedTransferCustomer")
+	return result, err
 }
 
 // ResignedTransferResultRequest 查询离职客户接替状态请求
@@ -251,10 +241,8 @@ func (r *Client) ResignedTransferResult(req *ResignedTransferResultRequest) (*Re
 		return nil, err
 	}
 	result := &ResignedTransferResultResponse{}
-	if err = util.DecodeWithError(response, result, "ResignedTransferResult"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "ResignedTransferResult")
+	return result, err
 }
 
 // GroupChatTransferRequest 分配离职成员的客户群请求
@@ -284,8 +272,6 @@ func (r *Client) GroupChatTransfer(req *GroupChatTransferRequest) (*GroupChatTra
 		return nil, err
 	}
 	result := &GroupChatTransferResponse{}
-	if err = util.DecodeWithError(response, result, "GroupChatTransfer"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GroupChatTransfer")
+	return result, err
 }

+ 4 - 8
work/invoice/invoice.go

@@ -86,10 +86,8 @@ func (r *Client) GetInvoiceInfo(req *GetInvoiceInfoRequest) (*GetInvoiceInfoResp
 		return nil, err
 	}
 	result := &GetInvoiceInfoResponse{}
-	if err = util.DecodeWithError(response, result, "GetInvoiceInfo"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetInvoiceInfo")
+	return result, err
 }
 
 // UpdateInvoiceStatusRequest 更新发票状态请求
@@ -184,8 +182,6 @@ func (r *Client) GetInvoiceInfoBatch(req *GetInvoiceInfoBatchRequest) (*GetInvoi
 		return nil, err
 	}
 	result := &GetInvoiceInfoBatchResponse{}
-	if err = util.DecodeWithError(response, result, "GetInvoiceInfoBatch"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetInvoiceInfoBatch")
+	return result, err
 }

+ 1 - 3
work/kf/callback.go

@@ -92,8 +92,6 @@ func (r *Client) GetCallbackMessage(encryptedMsg []byte) (msg CallbackMessage, e
 	if err != nil {
 		return msg, NewSDKErr(40016)
 	}
-	if err = xml.Unmarshal(bData, &msg); err != nil {
-		return msg, err
-	}
+	err = xml.Unmarshal(bData, &msg)
 	return msg, err
 }

+ 4 - 8
work/kf/statistic.go

@@ -59,10 +59,8 @@ func (r *Client) GetCorpStatistic(req *GetCorpStatisticRequest) (*GetCorpStatist
 		return nil, err
 	}
 	result := &GetCorpStatisticResponse{}
-	if err = util.DecodeWithError(response, result, "GetCorpStatistic"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetCorpStatistic")
+	return result, err
 }
 
 // GetServicerStatisticRequest 获取「客户数据统计」接待人员明细数据请求
@@ -120,8 +118,6 @@ func (r *Client) GetServicerStatistic(req *GetServicerStatisticRequest) (*GetSer
 		return nil, err
 	}
 	result := &GetServicerStatisticResponse{}
-	if err = util.DecodeWithError(response, result, "GetServicerStatistic"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetServicerStatistic")
+	return result, err
 }

+ 6 - 12
work/material/media.go

@@ -52,10 +52,8 @@ func (r *Client) UploadImg(filename string) (*UploadImgResponse, error) {
 		return nil, err
 	}
 	result := &UploadImgResponse{}
-	if err = util.DecodeWithError(response, result, "UploadImg"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "UploadImg")
+	return result, err
 }
 
 // UploadTempFile 上传临时素材
@@ -74,10 +72,8 @@ func (r *Client) UploadTempFile(filename string, mediaType string) (*UploadTempF
 		return nil, err
 	}
 	result := &UploadTempFileResponse{}
-	if err = util.DecodeWithError(response, result, "UploadTempFile"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "UploadTempFile")
+	return result, err
 }
 
 // UploadAttachment 上传附件资源
@@ -97,8 +93,6 @@ func (r *Client) UploadAttachment(filename string, mediaType string, attachmentT
 		return nil, err
 	}
 	result := &UploadAttachmentResponse{}
-	if err = util.DecodeWithError(response, result, "UploadAttachment"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "UploadAttachment")
+	return result, err
 }

+ 2 - 4
work/message/message.go

@@ -99,11 +99,9 @@ func (r *Client) Send(apiName string, request interface{}) (*SendResponse, error
 	}
 	// 按照结构体解析返回值
 	result := &SendResponse{}
-	if err = util.DecodeWithError(response, result, apiName); err != nil {
-		return nil, err
-	}
+	err = util.DecodeWithError(response, result, apiName)
 	// 返回数据
-	return result, nil
+	return result, err
 }
 
 // SendText 发送文本消息

+ 1 - 4
work/msgaudit/client_linux.go

@@ -149,10 +149,7 @@ func (s *Client) GetRawChatData(seq uint64, limit uint64, proxy string, passwd s
 
 	var data ChatDataResponse
 	err := json.Unmarshal(buf, &data)
-	if err != nil {
-		return ChatDataResponse{}, err
-	}
-	return data, nil
+	return data, err
 }
 
 // DecryptData 解析密文.企业微信自有解密内容

+ 4 - 8
work/oauth/oauth.go

@@ -123,10 +123,8 @@ func (ctr *Oauth) GetUserInfo(code string) (*GetUserInfoResponse, error) {
 		return nil, err
 	}
 	result := &GetUserInfoResponse{}
-	if err = util.DecodeWithError(response, result, "GetUserInfo"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetUserInfo")
+	return result, err
 }
 
 // GetUserDetailRequest 获取访问用户敏感信息请求
@@ -162,8 +160,6 @@ func (ctr *Oauth) GetUserDetail(req *GetUserDetailRequest) (*GetUserDetailRespon
 		return nil, err
 	}
 	result := &GetUserDetailResponse{}
-	if err = util.DecodeWithError(response, result, "GetUserDetail"); err != nil {
-		return nil, err
-	}
-	return result, nil
+	err = util.DecodeWithError(response, result, "GetUserDetail")
+	return result, err
 }