1
0
mirror of https://github.com/silenceper/wechat.git synced 2026-02-04 12:52:27 +08:00

Feature: improve code message subscribe (#739)

* fix

* feat(miniapp): 小程序订阅消息 (#429)

1. 用户订阅消息服务端回调处理
2. 用户订阅消息订阅通知事件推送

Co-authored-by: houseme <qzg40737@163.com>

* feat: improve subscribe msg

* feat: add v1.21 and feature branch

* feat: improve code for subscribe

* test

* test

* fix

* fix

* improve comment

* improve code for message

* improve code for unmarshal message

* improve code for message

---------

Co-authored-by: Ralph Maas <stuchilde@outlook.com>
This commit is contained in:
houseme
2023-11-02 09:39:10 +08:00
committed by GitHub
parent d4fd145bb5
commit 56a3734546
8 changed files with 292 additions and 73 deletions

View File

@@ -2,15 +2,15 @@ name: Go
on: on:
push: push:
branches: [ master,release-*,v2 ] branches: [ master,release-*,v2,feature/** ]
pull_request: pull_request:
branches: [ master,release-*,v2 ] branches: [ master,release-*,v2,feature/** ]
jobs: jobs:
golangci: golangci:
strategy: strategy:
matrix: matrix:
go-version: [ '1.16','1.17','1.18','1.19','1.20' ] go-version: [ '1.16','1.17','1.18','1.19','1.20','1.21' ]
name: golangci-lint name: golangci-lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -42,7 +42,7 @@ jobs:
# strategy set # strategy set
strategy: strategy:
matrix: matrix:
go: [ '1.16','1.17','1.18','1.19','1.20' ] go: [ '1.16','1.17','1.18','1.19','1.20','1.21' ]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View File

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

View File

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

View File

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

View File

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

View File

@@ -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{}
}

View File

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

View File

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