mirror of
https://github.com/silenceper/wechat.git
synced 2026-02-06 21:52:27 +08:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30c8e77246 | ||
|
|
6f6e95cfdb | ||
|
|
c806a0c172 | ||
|
|
c136b878ce | ||
|
|
d4a81916d5 | ||
|
|
ef1372b98a | ||
|
|
0d666b60ba | ||
|
|
e1122d42b0 | ||
|
|
be3f0d8bd5 | ||
|
|
66f9794d2f | ||
|
|
ee5f045b89 | ||
|
|
d35f0f0865 | ||
|
|
bbad169706 | ||
|
|
5927c26152 | ||
|
|
8ebff5c29c | ||
|
|
86ef690ecd | ||
|
|
ee85790123 |
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
@@ -1,8 +1,8 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
github: # silenceper
|
||||||
patreon: # Replace with a single Patreon username
|
patreon: # Replace with a single Patreon username
|
||||||
open_collective: # Replace with a single Open Collective username
|
open_collective: gowechat
|
||||||
ko_fi: # Replace with a single Ko-fi username
|
ko_fi: # Replace with a single Ko-fi username
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
|
|||||||
38
.github/workflows/ai-dev.yaml
vendored
Normal file
38
.github/workflows/ai-dev.yaml
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: Claude Code
|
||||||
|
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
pull_request_review_comment:
|
||||||
|
types: [created]
|
||||||
|
issues:
|
||||||
|
types: [opened, assigned]
|
||||||
|
pull_request_review:
|
||||||
|
types: [submitted]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
claude:
|
||||||
|
if: |
|
||||||
|
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||||
|
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||||
|
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
||||||
|
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: read
|
||||||
|
issues: read
|
||||||
|
id-token: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
- name: Run Claude Code
|
||||||
|
id: claude
|
||||||
|
uses: anthropics/claude-code-action@beta
|
||||||
|
env:
|
||||||
|
ANTHROPIC_BASE_URL: "${{ secrets.ANTHROPIC_BASE_URL }}"
|
||||||
|
with:
|
||||||
|
model: "${{ secrets.ANTHROPIC_MODEL }}"
|
||||||
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
17
README.md
17
README.md
@@ -1,12 +1,13 @@
|
|||||||
# WeChat SDK for Go
|
# WeChat SDK for Go
|
||||||
|
|
||||||

|

|
||||||
[](https://goreportcard.com/report/github.com/silenceper/wechat)
|
[](https://goreportcard.com/report/github.com/silenceper/wechat/v2)
|
||||||
[](https://pkg.go.dev/github.com/silenceper/wechat/v2?tab=doc)
|
[](https://pkg.go.dev/github.com/silenceper/wechat/v2?tab=doc)
|
||||||

|

|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
使用Golang开发的微信SDK,简单、易用。
|
使用Golang开发的微信SDK,简单、易用。
|
||||||
> 注意:当前版本为v2版本,v1版本已废弃
|
|
||||||
|
|
||||||
## 文档 && 例子
|
## 文档 && 例子
|
||||||
|
|
||||||
@@ -75,7 +76,13 @@ server.Send()
|
|||||||
- 提交issue,描述需要贡献的内容
|
- 提交issue,描述需要贡献的内容
|
||||||
- 完成更改后,提交PR
|
- 完成更改后,提交PR
|
||||||
|
|
||||||
## 公众号
|
|
||||||
|
## 感谢以下贡献者
|
||||||
|
|
||||||
|
<a href="https://opencollective.com/gowechat"><img src="https://opencollective.com/gowechat/contributors.svg?width=890" /></a>
|
||||||
|
|
||||||
|
|
||||||
|
## 作者公众号
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ const (
|
|||||||
getAnalysisVisitDistributionURL = "https://api.weixin.qq.com/datacube/getweanalysisappidvisitdistribution?access_token=%s"
|
getAnalysisVisitDistributionURL = "https://api.weixin.qq.com/datacube/getweanalysisappidvisitdistribution?access_token=%s"
|
||||||
// 访问页面
|
// 访问页面
|
||||||
getAnalysisVisitPageURL = "https://api.weixin.qq.com/datacube/getweanalysisappidvisitpage?access_token=%s"
|
getAnalysisVisitPageURL = "https://api.weixin.qq.com/datacube/getweanalysisappidvisitpage?access_token=%s"
|
||||||
|
// 获取小程序性能数据
|
||||||
|
getPerformanceDataURL = "https://api.weixin.qq.com/wxa/business/performance/boot?access_token=%s"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Analysis analyis 数据分析
|
// Analysis analyis 数据分析
|
||||||
@@ -315,3 +317,67 @@ func (analysis *Analysis) GetAnalysisVisitPage(beginDate, endDate string) (resul
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPerformanceDataRequest 获取小程序性能数据请求
|
||||||
|
type GetPerformanceDataRequest struct {
|
||||||
|
Module string `json:"module"`
|
||||||
|
Time PerformanceDataTime `json:"time"`
|
||||||
|
Params []PerformanceDataParams `json:"params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PerformanceDataTime 获取小程序性能数据开始和结束日期
|
||||||
|
type PerformanceDataTime struct {
|
||||||
|
BeginTimestamp int64 `json:"begin_timestamp"`
|
||||||
|
EndTimestamp int64 `json:"end_timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PerformanceDataParams 获取小程序性能数据查询条件
|
||||||
|
type PerformanceDataParams struct {
|
||||||
|
Field string `json:"field"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPerformanceDataResponse 获取小程序性能数据响应
|
||||||
|
type GetPerformanceDataResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
Body PerformanceDataBody `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PerformanceDataBody 性能数据
|
||||||
|
type PerformanceDataBody struct {
|
||||||
|
Tables []PerformanceDataTable `json:"tables"`
|
||||||
|
Count int64 `json:"count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PerformanceDataTable 数据数组
|
||||||
|
type PerformanceDataTable struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Lines []PerformanceDataTableLine `json:"lines"`
|
||||||
|
Zh string `json:"zh"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PerformanceDataTableLine 按时间排列的性能数据
|
||||||
|
type PerformanceDataTableLine struct {
|
||||||
|
Fields []PerformanceDataTableLineField `json:"fields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PerformanceDataTableLineField 单天的性能数据
|
||||||
|
type PerformanceDataTableLineField struct {
|
||||||
|
RefDate string `json:"refdate"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPerformanceData 获取小程序性能数据
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/data-analysis/others/getPerformanceData.html
|
||||||
|
func (analysis *Analysis) GetPerformanceData(req *GetPerformanceDataRequest) (res GetPerformanceDataResponse, err error) {
|
||||||
|
var accessToken string
|
||||||
|
if accessToken, err = analysis.GetAccessToken(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(fmt.Sprintf(getPerformanceDataURL, accessToken), req); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = util.DecodeWithError(response, &res, "GetPerformanceData")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
295
miniprogram/express/delivery.go
Normal file
295
miniprogram/express/delivery.go
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
package express
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/silenceper/wechat/v2/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 传运单接口,商户使用此接口向微信提供某交易单号对应的运单号。微信后台会跟踪运单的状态变化
|
||||||
|
openMsgTraceWaybillURL = "https://api.weixin.qq.com/cgi-bin/express/delivery/open_msg/trace_waybill?access_token=%s"
|
||||||
|
|
||||||
|
// 查询运单接口,商户在调用完trace_waybill接口后,可以使用本接口查询到对应运单的详情信息
|
||||||
|
openMsgQueryTraceURL = "https://api.weixin.qq.com/cgi-bin/express/delivery/open_msg/query_trace?access_token=%s"
|
||||||
|
|
||||||
|
// 更新物流信息,更新物品信息
|
||||||
|
openMsgUpdateWaybillGoodsURL = "https://api.weixin.qq.com/cgi-bin/express/delivery/open_msg/update_waybill_goods?access_token=%s"
|
||||||
|
|
||||||
|
// 传运单接口,商户使用此接口向微信提供某交易单号对应的运单号。微信后台会跟踪运单的状态变化,在关键物流节点给下单用户推送消息通知
|
||||||
|
openMsgFollowWaybillURL = "https://api.weixin.qq.com/cgi-bin/express/delivery/open_msg/follow_waybill?access_token=%s"
|
||||||
|
|
||||||
|
// 查运单接口,商户在调用完follow_waybill接口后,可以使用本接口查询到对应运单的详情信息
|
||||||
|
openMsgQueryFollowTraceURL = "https://api.weixin.qq.com/cgi-bin/express/delivery/open_msg/query_follow_trace?access_token=%s"
|
||||||
|
|
||||||
|
// 更新物品信息接口
|
||||||
|
openMsgUpdateFollowWaybillGoodsURL = "https://api.weixin.qq.com/cgi-bin/express/delivery/open_msg/update_follow_waybill_goods?access_token=%s"
|
||||||
|
|
||||||
|
// 获取运力id列表
|
||||||
|
openMsgGetDeliveryListURL = "https://api.weixin.qq.com/cgi-bin/express/delivery/open_msg/get_delivery_list?access_token=%s"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TraceWaybill 传运单
|
||||||
|
// https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/express_search.html#_2-%E6%8E%A5%E5%8F%A3%E5%88%97%E8%A1%A8
|
||||||
|
func (express *Express) TraceWaybill(ctx context.Context, in *TraceWaybillRequest) (res TraceWaybillResponse, err error) {
|
||||||
|
accessToken, err := express.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(openMsgTraceWaybillURL, accessToken)
|
||||||
|
response, err := util.PostJSONContext(ctx, uri, in)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用通用方法返回错误
|
||||||
|
err = util.DecodeWithError(response, &res, "TraceWaybill")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryTrace 查询运单详情信息
|
||||||
|
// https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/express_search.html#_2-%E6%8E%A5%E5%8F%A3%E5%88%97%E8%A1%A8
|
||||||
|
func (express *Express) QueryTrace(ctx context.Context, in *QueryTraceRequest) (res QueryTraceResponse, err error) {
|
||||||
|
accessToken, err := express.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(openMsgQueryTraceURL, accessToken)
|
||||||
|
response, err := util.PostJSONContext(ctx, uri, in)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用通用方法返回错误
|
||||||
|
err = util.DecodeWithError(response, &res, "QueryTrace")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateWaybillGoods 更新物品信息
|
||||||
|
// https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/express_search.html#_2-%E6%8E%A5%E5%8F%A3%E5%88%97%E8%A1%A8
|
||||||
|
func (express *Express) UpdateWaybillGoods(ctx context.Context, in *UpdateWaybillGoodsRequest) (err error) {
|
||||||
|
accessToken, err := express.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(openMsgUpdateWaybillGoodsURL, accessToken)
|
||||||
|
response, err := util.PostJSONContext(ctx, uri, in)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用通用方法返回错误
|
||||||
|
err = util.DecodeWithCommonError(response, "UpdateWaybillGoods")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// FollowWaybill 传运单
|
||||||
|
// https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/express_open_msg.html#_4-1%E3%80%81%E4%BC%A0%E8%BF%90%E5%8D%95%E6%8E%A5%E5%8F%A3-follow-waybill
|
||||||
|
func (express *Express) FollowWaybill(ctx context.Context, in *FollowWaybillRequest) (res FollowWaybillResponse, err error) {
|
||||||
|
accessToken, err := express.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(openMsgFollowWaybillURL, accessToken)
|
||||||
|
response, err := util.PostJSONContext(ctx, uri, in)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用通用方法返回错误
|
||||||
|
err = util.DecodeWithError(response, &res, "FollowWaybill")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryFollowTrace 查询运单详情信息
|
||||||
|
// https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/express_open_msg.html#_4-2%E3%80%81%E6%9F%A5%E8%BF%90%E5%8D%95%E6%8E%A5%E5%8F%A3-query-follow-trace
|
||||||
|
func (express *Express) QueryFollowTrace(ctx context.Context, in *QueryFollowTraceRequest) (res QueryFollowTraceResponse, err error) {
|
||||||
|
accessToken, err := express.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(openMsgQueryFollowTraceURL, accessToken)
|
||||||
|
response, err := util.PostJSONContext(ctx, uri, in)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用通用方法返回错误
|
||||||
|
err = util.DecodeWithError(response, &res, "QueryFollowTrace")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateFollowWaybillGoods 更新物品信息
|
||||||
|
// https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/express_open_msg.html#_4-3%E3%80%81%E6%9B%B4%E6%96%B0%E7%89%A9%E5%93%81%E4%BF%A1%E6%81%AF%E6%8E%A5%E5%8F%A3-update-follow-waybill-goods
|
||||||
|
func (express *Express) UpdateFollowWaybillGoods(ctx context.Context, in *UpdateFollowWaybillGoodsRequest) (err error) {
|
||||||
|
accessToken, err := express.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(openMsgUpdateFollowWaybillGoodsURL, accessToken)
|
||||||
|
response, err := util.PostJSONContext(ctx, uri, in)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用通用方法返回错误
|
||||||
|
err = util.DecodeWithCommonError(response, "UpdateFollowWaybillGoods")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDeliveryList 获取运力id列表
|
||||||
|
// https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/express_open_msg.html#_4-4%E8%8E%B7%E5%8F%96%E8%BF%90%E5%8A%9Bid%E5%88%97%E8%A1%A8get-delivery-list
|
||||||
|
func (express *Express) GetDeliveryList(ctx context.Context) (res GetDeliveryListResponse, err error) {
|
||||||
|
accessToken, err := express.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(openMsgGetDeliveryListURL, accessToken)
|
||||||
|
response, err := util.PostJSONContext(ctx, uri, map[string]interface{}{})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用通用方法返回错误
|
||||||
|
err = util.DecodeWithError(response, &res, "GetDeliveryList")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceWaybillRequest 传运单接口请求参数
|
||||||
|
type TraceWaybillRequest struct {
|
||||||
|
GoodsInfo FollowWaybillGoodsInfo `json:"goods_info"` // 必选,商品信息
|
||||||
|
Openid string `json:"openid"` // 必选,用户openid
|
||||||
|
SenderPhone string `json:"sender_phone"` // 寄件人手机号
|
||||||
|
ReceiverPhone string `json:"receiver_phone"` // 必选,收件人手机号,部分运力需要用户手机号作为查单依据
|
||||||
|
DeliveryID string `json:"delivery_id"` // 运力id(运单号所属运力公司id)
|
||||||
|
WaybillID string `json:"waybill_id"` // 必选,运单号
|
||||||
|
TransID string `json:"trans_id"` // 必选,交易单号(微信支付生成的交易单号,一般以420开头)
|
||||||
|
OrderDetailPath string `json:"order_detail_path"` // 订单详情页地址
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceWaybillResponse 传运单接口返回参数
|
||||||
|
type TraceWaybillResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
WaybillToken string `json:"waybill_token"` // 查询id
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryTraceRequest 查询运单详情接口请求参数
|
||||||
|
type QueryTraceRequest struct {
|
||||||
|
WaybillToken string `json:"waybill_token"` // 必选,查询id
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryTraceResponse 查询运单详情接口返回参数
|
||||||
|
type QueryTraceResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
WaybillInfo FlowWaybillInfo `json:"waybill_info"` // 运单信息
|
||||||
|
ShopInfo FollowWaybillShopInfo `json:"shop_info"` // 商品信息
|
||||||
|
DeliveryInfo FlowWaybillDeliveryInfo `json:"delivery_info"` // 运力信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateWaybillGoodsRequest 更新物品信息接口请求参数
|
||||||
|
type UpdateWaybillGoodsRequest struct {
|
||||||
|
WaybillToken string `json:"waybill_token"` // 必选,查询id
|
||||||
|
GoodsInfo FollowWaybillGoodsInfo `json:"goods_info"` // 必选,商品信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// FollowWaybillRequest 传运单接口请求参数
|
||||||
|
type FollowWaybillRequest struct {
|
||||||
|
GoodsInfo FollowWaybillGoodsInfo `json:"goods_info"` // 必选,商品信息
|
||||||
|
Openid string `json:"openid"` // 必选,用户openid
|
||||||
|
SenderPhone string `json:"sender_phone"` // 寄件人手机号
|
||||||
|
ReceiverPhone string `json:"receiver_phone"` // 必选,收件人手机号,部分运力需要用户手机号作为查单依据
|
||||||
|
DeliveryID string `json:"delivery_id"` // 运力id(运单号所属运力公司id)
|
||||||
|
WaybillID string `json:"waybill_id"` // 必选,运单号
|
||||||
|
TransID string `json:"trans_id"` // 必选,交易单号(微信支付生成的交易单号,一般以420开头)
|
||||||
|
OrderDetailPath string `json:"order_detail_path"` // 订单详情页地址
|
||||||
|
}
|
||||||
|
|
||||||
|
// FollowWaybillGoodsInfo 商品信息
|
||||||
|
type FollowWaybillGoodsInfo struct {
|
||||||
|
DetailList []FollowWaybillGoodsInfoItem `json:"detail_list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FollowWaybillShopInfo 商品信息
|
||||||
|
type FollowWaybillShopInfo struct {
|
||||||
|
GoodsInfo FollowWaybillGoodsInfo `json:"goods_info"` // 商品信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// FollowWaybillGoodsInfoItem 商品信息详情
|
||||||
|
type FollowWaybillGoodsInfoItem struct {
|
||||||
|
GoodsName string `json:"goods_name"` // 必选,商品名称(最大长度为utf-8编码下的60个字符)
|
||||||
|
GoodsImgURL string `json:"goods_img_url"` // 必选,商品图片url
|
||||||
|
GoodsDesc string `json:"goods_desc,omitempty"` // 商品详情描述,不传默认取“商品名称”值,最多40汉字
|
||||||
|
}
|
||||||
|
|
||||||
|
// FollowWaybillResponse 传运单接口返回参数
|
||||||
|
type FollowWaybillResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
WaybillToken string `json:"waybill_token"` // 查询id
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryFollowTraceRequest 查询运单详情信息请求参数
|
||||||
|
type QueryFollowTraceRequest struct {
|
||||||
|
WaybillToken string `json:"waybill_token"` // 必选,查询id
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryFollowTraceResponse 查询运单详情信息返回参数
|
||||||
|
type QueryFollowTraceResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
WaybillInfo FlowWaybillInfo `json:"waybill_info"` // 运单信息
|
||||||
|
ShopInfo FollowWaybillShopInfo `json:"shop_info"` // 商品信息
|
||||||
|
DeliveryInfo FlowWaybillDeliveryInfo `json:"delivery_info"` // 运力信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlowWaybillInfo 运单信息
|
||||||
|
type FlowWaybillInfo struct {
|
||||||
|
WaybillID string `json:"waybill_id"` // 运单号
|
||||||
|
Status WaybillStatus `json:"status"` // 运单状态
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateFollowWaybillGoodsRequest 修改运单商品信息请求参数
|
||||||
|
type UpdateFollowWaybillGoodsRequest struct {
|
||||||
|
WaybillToken string `json:"waybill_token"` // 必选,查询id
|
||||||
|
GoodsInfo FollowWaybillGoodsInfo `json:"goods_info"` // 必选,商品信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDeliveryListResponse 获取运力id列表返回参数
|
||||||
|
type GetDeliveryListResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
DeliveryList []FlowWaybillDeliveryInfo `json:"delivery_list"` // 运力公司列表
|
||||||
|
Count int `json:"count"` // 运力公司个数
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlowWaybillDeliveryInfo 运力公司信息
|
||||||
|
type FlowWaybillDeliveryInfo struct {
|
||||||
|
DeliveryID string `json:"delivery_id"` // 运力公司id
|
||||||
|
DeliveryName string `json:"delivery_name"` // 运力公司名称
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaybillStatus 运单状态
|
||||||
|
type WaybillStatus int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// WaybillStatusNotExist 运单不存在或者未揽收
|
||||||
|
WaybillStatusNotExist WaybillStatus = iota
|
||||||
|
// WaybillStatusPicked 已揽件
|
||||||
|
WaybillStatusPicked
|
||||||
|
// WaybillStatusTransporting 运输中
|
||||||
|
WaybillStatusTransporting
|
||||||
|
// WaybillStatusDispatching 派件中
|
||||||
|
WaybillStatusDispatching
|
||||||
|
// WaybillStatusSigned 已签收
|
||||||
|
WaybillStatusSigned
|
||||||
|
// WaybillStatusException 异常
|
||||||
|
WaybillStatusException
|
||||||
|
// WaybillStatusSignedByOthers 代签收
|
||||||
|
WaybillStatusSignedByOthers
|
||||||
|
)
|
||||||
16
miniprogram/express/express.go
Normal file
16
miniprogram/express/express.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package express
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/silenceper/wechat/v2/miniprogram/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Express 微信物流服务
|
||||||
|
// https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/introduction.html
|
||||||
|
type Express struct {
|
||||||
|
*context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewExpress init
|
||||||
|
func NewExpress(ctx *context.Context) *Express {
|
||||||
|
return &Express{ctx}
|
||||||
|
}
|
||||||
@@ -556,7 +556,7 @@ type SubscribeMsgSentEvent struct {
|
|||||||
type SubscribeMsgSentList struct {
|
type SubscribeMsgSentList struct {
|
||||||
TemplateID string `xml:"TemplateId" json:"TemplateId"`
|
TemplateID string `xml:"TemplateId" json:"TemplateId"`
|
||||||
MsgID string `xml:"MsgID" json:"MsgID"`
|
MsgID string `xml:"MsgID" json:"MsgID"`
|
||||||
ErrorCode int `xml:"ErrorCode" json:"ErrorCode"`
|
ErrorCode string `xml:"ErrorCode" json:"ErrorCode"`
|
||||||
ErrorStatus string `xml:"ErrorStatus" json:"ErrorStatus"`
|
ErrorStatus string `xml:"ErrorStatus" json:"ErrorStatus"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ import (
|
|||||||
"github.com/silenceper/wechat/v2/miniprogram/content"
|
"github.com/silenceper/wechat/v2/miniprogram/content"
|
||||||
"github.com/silenceper/wechat/v2/miniprogram/context"
|
"github.com/silenceper/wechat/v2/miniprogram/context"
|
||||||
"github.com/silenceper/wechat/v2/miniprogram/encryptor"
|
"github.com/silenceper/wechat/v2/miniprogram/encryptor"
|
||||||
|
"github.com/silenceper/wechat/v2/miniprogram/express"
|
||||||
"github.com/silenceper/wechat/v2/miniprogram/message"
|
"github.com/silenceper/wechat/v2/miniprogram/message"
|
||||||
"github.com/silenceper/wechat/v2/miniprogram/minidrama"
|
"github.com/silenceper/wechat/v2/miniprogram/minidrama"
|
||||||
|
"github.com/silenceper/wechat/v2/miniprogram/operation"
|
||||||
"github.com/silenceper/wechat/v2/miniprogram/order"
|
"github.com/silenceper/wechat/v2/miniprogram/order"
|
||||||
"github.com/silenceper/wechat/v2/miniprogram/privacy"
|
"github.com/silenceper/wechat/v2/miniprogram/privacy"
|
||||||
"github.com/silenceper/wechat/v2/miniprogram/qrcode"
|
"github.com/silenceper/wechat/v2/miniprogram/qrcode"
|
||||||
@@ -179,3 +181,13 @@ func (miniProgram *MiniProgram) GetRedPacketCover() *redpacketcover.RedPacketCov
|
|||||||
func (miniProgram *MiniProgram) GetUpdatableMessage() *message.UpdatableMessage {
|
func (miniProgram *MiniProgram) GetUpdatableMessage() *message.UpdatableMessage {
|
||||||
return message.NewUpdatableMessage(miniProgram.ctx)
|
return message.NewUpdatableMessage(miniProgram.ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetOperation 小程序运维中心
|
||||||
|
func (miniProgram *MiniProgram) GetOperation() *operation.Operation {
|
||||||
|
return operation.NewOperation(miniProgram.ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExpress 微信物流服务
|
||||||
|
func (miniProgram *MiniProgram) GetExpress() *express.Express {
|
||||||
|
return express.NewExpress(miniProgram.ctx)
|
||||||
|
}
|
||||||
|
|||||||
456
miniprogram/operation/operation.go
Normal file
456
miniprogram/operation/operation.go
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
package operation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/silenceper/wechat/v2/miniprogram/context"
|
||||||
|
"github.com/silenceper/wechat/v2/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// getDomainInfoURL 查询域名配置
|
||||||
|
getDomainInfoURL = "https://api.weixin.qq.com/wxa/getwxadevinfo?access_token=%s"
|
||||||
|
// getPerformanceURL 获取性能数据
|
||||||
|
getPerformanceURL = "https://api.weixin.qq.com/wxaapi/log/get_performance?access_token=%s"
|
||||||
|
// getSceneListURL 获取访问来源
|
||||||
|
getSceneListURL = "https://api.weixin.qq.com/wxaapi/log/get_scene?access_token=%s"
|
||||||
|
// getVersionListURL 获取客户端版本
|
||||||
|
getVersionListURL = "https://api.weixin.qq.com/wxaapi/log/get_client_version?access_token=%s"
|
||||||
|
// realTimeLogSearchURL 查询实时日志
|
||||||
|
realTimeLogSearchURL = "https://api.weixin.qq.com/wxaapi/userlog/userlog_search?%s"
|
||||||
|
// getFeedbackListURL 获取用户反馈列表
|
||||||
|
getFeedbackListURL = "https://api.weixin.qq.com/wxaapi/feedback/list?%s"
|
||||||
|
// getJsErrDetailURL 查询js错误详情
|
||||||
|
getJsErrDetailURL = "https://api.weixin.qq.com/wxaapi/log/jserr_detail?access_token=%s"
|
||||||
|
// getJsErrListURL 查询错误列表
|
||||||
|
getJsErrListURL = "https://api.weixin.qq.com/wxaapi/log/jserr_list?access_token=%s"
|
||||||
|
// getGrayReleasePlanURL 获取分阶段发布详情
|
||||||
|
getGrayReleasePlanURL = "https://api.weixin.qq.com/wxa/getgrayreleaseplan?access_token=%s"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Operation 运维中心
|
||||||
|
type Operation struct {
|
||||||
|
*context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOperation 实例化
|
||||||
|
func NewOperation(ctx *context.Context) *Operation {
|
||||||
|
return &Operation{ctx}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDomainInfoRequest 查询域名配置请求
|
||||||
|
type GetDomainInfoRequest struct {
|
||||||
|
Action string `json:"action"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDomainInfoResponse 查询域名配置响应
|
||||||
|
type GetDomainInfoResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
RequestDomain []string `json:"requestdomain"`
|
||||||
|
WsRequestDomain []string `json:"wsrequestdomain"`
|
||||||
|
UploadDomain []string `json:"uploaddomain"`
|
||||||
|
DownloadDomain []string `json:"downloaddomain"`
|
||||||
|
UDPDomain []string `json:"udpdomain"`
|
||||||
|
BizDomain []string `json:"bizdomain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDomainInfo 查询域名配置
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/operation/getDomainInfo.html
|
||||||
|
func (o *Operation) GetDomainInfo(req *GetDomainInfoRequest) (res GetDomainInfoResponse, err error) {
|
||||||
|
var accessToken string
|
||||||
|
if accessToken, err = o.GetAccessToken(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(fmt.Sprintf(getDomainInfoURL, accessToken), req); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = util.DecodeWithError(response, &res, "GetDomainInfo")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPerformanceRequest 获取性能数据请求
|
||||||
|
type GetPerformanceRequest struct {
|
||||||
|
CostTimeType int64 `json:"cost_time_type"`
|
||||||
|
DefaultStartTime int64 `json:"default_start_time"`
|
||||||
|
DefaultEndTime int64 `json:"default_end_time"`
|
||||||
|
Device string `json:"device"`
|
||||||
|
IsDownloadCode string `json:"is_download_code"`
|
||||||
|
Scene string `json:"scene"`
|
||||||
|
NetworkType string `json:"networktype"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPerformanceResponse 获取性能数据响应
|
||||||
|
type GetPerformanceResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
DefaultTimeData string `json:"default_time_data"`
|
||||||
|
CompareTimeData string `json:"compare_time_data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PerformanceDefaultTimeData 查询数据
|
||||||
|
type PerformanceDefaultTimeData struct {
|
||||||
|
List []DefaultTimeDataItem `json:"list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultTimeDataItem 查询数据
|
||||||
|
type DefaultTimeDataItem struct {
|
||||||
|
RefData string `json:"ref_data"`
|
||||||
|
CostTimeType int64 `json:"cost_time_type"`
|
||||||
|
CostTime int64 `json:"cost_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPerformance 获取性能数据
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/operation/getPerformance.html
|
||||||
|
func (o *Operation) GetPerformance(req *GetPerformanceRequest) (res GetPerformanceResponse, err error) {
|
||||||
|
var accessToken string
|
||||||
|
if accessToken, err = o.GetAccessToken(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(fmt.Sprintf(getPerformanceURL, accessToken), req); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = util.DecodeWithError(response, &res, "GetPerformance")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSceneListResponse 获取访问来源响应
|
||||||
|
type GetSceneListResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
Scene []Scene `json:"scene"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scene 访问来源
|
||||||
|
type Scene struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSceneList 获取访问来源
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/operation/getSceneList.html
|
||||||
|
func (o *Operation) GetSceneList() (res GetSceneListResponse, err error) {
|
||||||
|
var accessToken string
|
||||||
|
if accessToken, err = o.GetAccessToken(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.HTTPGet(fmt.Sprintf(getSceneListURL, accessToken)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = util.DecodeWithError(response, &res, "GetSceneList")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersionListResponse 获取客户端版本响应
|
||||||
|
type GetVersionListResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
CvList []ClientVersion `json:"cvlist"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientVersion 客户端版本
|
||||||
|
type ClientVersion struct {
|
||||||
|
Type int64 `json:"type"`
|
||||||
|
ClientVersionList []string `json:"client_version_list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersionList 获取客户端版本
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/operation/getVersionList.html
|
||||||
|
func (o *Operation) GetVersionList() (res GetVersionListResponse, err error) {
|
||||||
|
var accessToken string
|
||||||
|
if accessToken, err = o.GetAccessToken(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.HTTPGet(fmt.Sprintf(getVersionListURL, accessToken)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = util.DecodeWithError(response, &res, "GetVersionList")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RealTimeLogSearchRequest 查询实时日志请求
|
||||||
|
type RealTimeLogSearchRequest struct {
|
||||||
|
Date string
|
||||||
|
BeginTime int64
|
||||||
|
EndTime int64
|
||||||
|
Start int64
|
||||||
|
Limit int64
|
||||||
|
Level int64
|
||||||
|
TraceID string
|
||||||
|
URL string
|
||||||
|
ID string
|
||||||
|
FilterMsg string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RealTimeLogSearchResponse 查询实时日志响应
|
||||||
|
type RealTimeLogSearchResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
Data RealTimeLogSearchData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RealTimeLogSearchData 日志数据和日志条数总量
|
||||||
|
type RealTimeLogSearchData struct {
|
||||||
|
List []RealTimeLogSearchDataList `json:"list"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RealTimeLogSearchDataList 日志数据列表
|
||||||
|
type RealTimeLogSearchDataList struct {
|
||||||
|
Level int64 `json:"level"`
|
||||||
|
LibraryVersion string `json:"libraryVersion"`
|
||||||
|
ClientVersion string `json:"clientVersion"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Platform int64 `json:"platform"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
TraceID string `json:"traceid"`
|
||||||
|
FilterMsg string `json:"filterMsg"`
|
||||||
|
Msg []RealTimeLogSearchDataListMsg `json:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RealTimeLogSearchDataListMsg 日志内容数组
|
||||||
|
type RealTimeLogSearchDataListMsg struct {
|
||||||
|
Time int64 `json:"time"`
|
||||||
|
Level int64 `json:"level"`
|
||||||
|
Msg []string `json:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RealTimeLogSearch 查询实时日志
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/operation/realtimelogSearch.html
|
||||||
|
func (o *Operation) RealTimeLogSearch(req *RealTimeLogSearchRequest) (res RealTimeLogSearchResponse, err error) {
|
||||||
|
var accessToken string
|
||||||
|
if accessToken, err = o.GetAccessToken(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"access_token": accessToken,
|
||||||
|
"date": req.Date,
|
||||||
|
"begintime": req.BeginTime,
|
||||||
|
"endtime": req.EndTime,
|
||||||
|
}
|
||||||
|
if req.Start > 0 {
|
||||||
|
params["start"] = req.Start
|
||||||
|
}
|
||||||
|
if req.Limit > 0 {
|
||||||
|
params["limit"] = req.Limit
|
||||||
|
}
|
||||||
|
if req.TraceID != "" {
|
||||||
|
params["traceId"] = req.TraceID
|
||||||
|
}
|
||||||
|
if req.URL != "" {
|
||||||
|
params["url"] = req.URL
|
||||||
|
}
|
||||||
|
if req.ID != "" {
|
||||||
|
params["id"] = req.ID
|
||||||
|
}
|
||||||
|
if req.FilterMsg != "" {
|
||||||
|
params["filterMsg"] = req.FilterMsg
|
||||||
|
}
|
||||||
|
if req.Level > 0 {
|
||||||
|
params["level"] = req.Level
|
||||||
|
}
|
||||||
|
query := util.Query(params)
|
||||||
|
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.HTTPGet(fmt.Sprintf(realTimeLogSearchURL, query)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = util.DecodeWithError(response, &res, "RealTimeLogSearch")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFeedbackListRequest 获取用户反馈列表请求
|
||||||
|
type GetFeedbackListRequest struct {
|
||||||
|
Page int64
|
||||||
|
Num int64
|
||||||
|
Type int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFeedbackListResponse 获取用户反馈列表响应
|
||||||
|
type GetFeedbackListResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
TotalNum int64 `json:"total_num"`
|
||||||
|
List []Feedback `json:"list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feedback 反馈列表
|
||||||
|
type Feedback struct {
|
||||||
|
RecordID int64 `json:"record_id"`
|
||||||
|
CreateTime int64 `json:"create_time"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
OpenID string `json:"openid"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
HeadURL string `json:"head_url"`
|
||||||
|
Type int64 `json:"type"`
|
||||||
|
MediaIDS []string `json:"mediaIds"`
|
||||||
|
SystemInfo string `json:"systemInfo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFeedbackList 获取用户反馈列表
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/operation/getFeedback.html
|
||||||
|
func (o *Operation) GetFeedbackList(req *GetFeedbackListRequest) (res GetFeedbackListResponse, err error) {
|
||||||
|
var accessToken string
|
||||||
|
if accessToken, err = o.GetAccessToken(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"access_token": accessToken,
|
||||||
|
"page": req.Page,
|
||||||
|
"num": req.Num,
|
||||||
|
}
|
||||||
|
if req.Type > 0 {
|
||||||
|
params["type"] = req.Type
|
||||||
|
}
|
||||||
|
query := util.Query(params)
|
||||||
|
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.HTTPGet(fmt.Sprintf(getFeedbackListURL, query)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = util.DecodeWithError(response, &res, "GetFeedbackList")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetJsErrDetailRequest 查询js错误详情请求
|
||||||
|
type GetJsErrDetailRequest struct {
|
||||||
|
StartTime string `json:"startTime"`
|
||||||
|
EndTime string `json:"endTime"`
|
||||||
|
ErrorMsgMd5 string `json:"errorMsgMd5"`
|
||||||
|
ErrorStackMd5 string `json:"errorStackMd5"`
|
||||||
|
AppVersion string `json:"appVersion"`
|
||||||
|
SdkVersion string `json:"sdkVersion"`
|
||||||
|
OsName string `json:"osName"`
|
||||||
|
ClientVersion string `json:"clientVersion"`
|
||||||
|
OpenID string `json:"openid"`
|
||||||
|
Offset int64 `json:"offset"`
|
||||||
|
Limit int64 `json:"limit"`
|
||||||
|
Desc string `json:"desc"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetJsErrDetailResponse 查询js错误详情响应
|
||||||
|
type GetJsErrDetailResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
TotalCount int64 `json:"totalCount"`
|
||||||
|
OpenID string `json:"openid"`
|
||||||
|
Data []JsErrDetailData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JsErrDetailData 错误列表
|
||||||
|
type JsErrDetailData struct {
|
||||||
|
Count string `json:"Count"`
|
||||||
|
SdkVersion string `json:"sdkVersion"`
|
||||||
|
ClientVersion string `json:"ClientVersion"`
|
||||||
|
ErrorStackMd5 string `json:"errorStackMd5"`
|
||||||
|
TimeStamp string `json:"TimeStamp"`
|
||||||
|
AppVersion string `json:"appVersion"`
|
||||||
|
ErrorMsgMd5 string `json:"errorMsgMd5"`
|
||||||
|
ErrorMsg string `json:"errorMsg"`
|
||||||
|
ErrorStack string `json:"errorStack"`
|
||||||
|
Ds string `json:"Ds"`
|
||||||
|
OsName string `json:"OsName"`
|
||||||
|
OpenID string `json:"openId"`
|
||||||
|
PluginVersion string `json:"pluginversion"`
|
||||||
|
AppID string `json:"appId"`
|
||||||
|
DeviceModel string `json:"DeviceModel"`
|
||||||
|
Source string `json:"source"`
|
||||||
|
Route string `json:"route"`
|
||||||
|
Uin string `json:"Uin"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetJsErrDetail 查询js错误详情
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/operation/getJsErrDetail.html
|
||||||
|
func (o *Operation) GetJsErrDetail(req *GetJsErrDetailRequest) (res GetJsErrDetailResponse, err error) {
|
||||||
|
var accessToken string
|
||||||
|
if accessToken, err = o.GetAccessToken(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(fmt.Sprintf(getJsErrDetailURL, accessToken), req); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = util.DecodeWithError(response, &res, "GetJsErrDetail")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetJsErrListRequest 查询错误列表请求
|
||||||
|
type GetJsErrListRequest struct {
|
||||||
|
AppVersion string `json:"appVersion"`
|
||||||
|
ErrType string `json:"errType"`
|
||||||
|
StartTime string `json:"startTime"`
|
||||||
|
EndTime string `json:"endTime"`
|
||||||
|
Keyword string `json:"keyword"`
|
||||||
|
OpenID string `json:"openid"`
|
||||||
|
OrderBy string `json:"orderby"`
|
||||||
|
Desc string `json:"desc"`
|
||||||
|
Offset int64 `json:"offset"`
|
||||||
|
Limit int64 `json:"limit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetJsErrListResponse 查询错误列表响应
|
||||||
|
type GetJsErrListResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
TotalCount int64 `json:"totalCount"`
|
||||||
|
OpenID string `json:"openid"`
|
||||||
|
Data []JsErrListData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JsErrListData 错误列表
|
||||||
|
type JsErrListData struct {
|
||||||
|
ErrorMsgMd5 string `json:"errorMsgMd5"`
|
||||||
|
ErrorMsg string `json:"errorMsg"`
|
||||||
|
Uv int64 `json:"uv"`
|
||||||
|
Pv int64 `json:"pv"`
|
||||||
|
ErrorStackMd5 string `json:"errorStackMd5"`
|
||||||
|
ErrorStack string `json:"errorStack"`
|
||||||
|
PvPercent string `json:"pvPercent"`
|
||||||
|
UvPercent string `json:"uvPercent"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetJsErrList 查询错误列表
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/operation/getJsErrList.html
|
||||||
|
func (o *Operation) GetJsErrList(req *GetJsErrListRequest) (res GetJsErrListResponse, err error) {
|
||||||
|
var accessToken string
|
||||||
|
if accessToken, err = o.GetAccessToken(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(fmt.Sprintf(getJsErrListURL, accessToken), req); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = util.DecodeWithError(response, &res, "GetJsErrList")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGrayReleasePlanResponse 获取分阶段发布详情响应
|
||||||
|
type GetGrayReleasePlanResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
GrayReleasePlan GrayReleasePlanDetail `json:"gray_release_plan"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GrayReleasePlanDetail 分阶段发布计划详情
|
||||||
|
type GrayReleasePlanDetail struct {
|
||||||
|
Status int64 `json:"status"`
|
||||||
|
CreateTimestamp int64 `json:"create_timestamp"`
|
||||||
|
GrayPercentage int64 `json:"gray_percentage"`
|
||||||
|
SupportExperiencerFirst bool `json:"support_experiencer_first"`
|
||||||
|
SupportDebugerFirst bool `json:"support_debuger_first"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGrayReleasePlan 获取分阶段发布详情
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/operation/getGrayReleasePlan.html
|
||||||
|
func (o *Operation) GetGrayReleasePlan() (res GetGrayReleasePlanResponse, err error) {
|
||||||
|
var accessToken string
|
||||||
|
if accessToken, err = o.GetAccessToken(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.HTTPGet(fmt.Sprintf(getGrayReleasePlanURL, accessToken)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = util.DecodeWithError(response, &res, "GetGrayReleasePlan")
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -68,3 +68,18 @@ func DecodeWithError(response []byte, obj interface{}, apiName string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleFileResponse 通用处理微信等接口返回:有时 JSON 错误,有时文件内容
|
||||||
|
func HandleFileResponse(response []byte, apiName string) ([]byte, error) {
|
||||||
|
var commErr CommonError
|
||||||
|
if err := json.Unmarshal(response, &commErr); err == nil {
|
||||||
|
// 能解析成 JSON,判断是否为错误
|
||||||
|
if commErr.ErrCode != 0 {
|
||||||
|
commErr.apiName = apiName
|
||||||
|
return nil, &commErr
|
||||||
|
}
|
||||||
|
// 能解析成 JSON 且没错误码,极少情况(比如微信返回的业务数据是 JSON 但无 errcode 字段),可根据需要调整
|
||||||
|
}
|
||||||
|
// 不能解析成 JSON,或没错误码,直接返回原始内容
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|||||||
15
util/http.go
15
util/http.go
@@ -291,7 +291,20 @@ func httpWithTLS(rootCa, key string) (*http.Client, error) {
|
|||||||
config := &tls.Config{
|
config := &tls.Config{
|
||||||
Certificates: []tls.Certificate{cert},
|
Certificates: []tls.Certificate{cert},
|
||||||
}
|
}
|
||||||
trans := (DefaultHTTPClient.Transport.(*http.Transport)).Clone()
|
|
||||||
|
// 安全地获取 *http.Transport
|
||||||
|
var trans *http.Transport
|
||||||
|
// 尝试从 DefaultHTTPClient 获取 Transport,如果失败则使用默认值
|
||||||
|
if DefaultHTTPClient.Transport != nil {
|
||||||
|
if t, ok := DefaultHTTPClient.Transport.(*http.Transport); ok {
|
||||||
|
trans = t.Clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果无法获取有效的 Transport,使用默认值
|
||||||
|
if trans == nil {
|
||||||
|
trans = http.DefaultTransport.(*http.Transport).Clone()
|
||||||
|
}
|
||||||
|
|
||||||
trans.TLSClientConfig = config
|
trans.TLSClientConfig = config
|
||||||
trans.DisableCompression = true
|
trans.DisableCompression = true
|
||||||
client = &http.Client{Transport: trans}
|
client = &http.Client{Transport: trans}
|
||||||
|
|||||||
81
util/http_test.go
Normal file
81
util/http_test.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestHttpWithTLS_NilTransport tests the scenario where DefaultHTTPClient.Transport is nil
|
||||||
|
func TestHttpWithTLS_NilTransport(t *testing.T) {
|
||||||
|
// Save original transport
|
||||||
|
originalTransport := DefaultHTTPClient.Transport
|
||||||
|
defer func() {
|
||||||
|
DefaultHTTPClient.Transport = originalTransport
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Set Transport to nil to simulate the bug scenario
|
||||||
|
DefaultHTTPClient.Transport = nil
|
||||||
|
|
||||||
|
// This should not panic after the fix
|
||||||
|
// Note: This will fail due to invalid cert path, but shouldn't panic on type assertion
|
||||||
|
_, err := httpWithTLS("./testdata/invalid_cert.p12", "password")
|
||||||
|
|
||||||
|
// We expect an error (cert file not found), but NOT a panic
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error due to invalid cert path, but got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHttpWithTLS_CustomTransport tests the scenario where DefaultHTTPClient has a custom Transport
|
||||||
|
func TestHttpWithTLS_CustomTransport(t *testing.T) {
|
||||||
|
// Save original transport
|
||||||
|
originalTransport := DefaultHTTPClient.Transport
|
||||||
|
defer func() {
|
||||||
|
DefaultHTTPClient.Transport = originalTransport
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Set a custom http.Transport
|
||||||
|
customTransport := &http.Transport{
|
||||||
|
MaxIdleConns: 100,
|
||||||
|
}
|
||||||
|
DefaultHTTPClient.Transport = customTransport
|
||||||
|
|
||||||
|
// This should not panic
|
||||||
|
_, err := httpWithTLS("./testdata/invalid_cert.p12", "password")
|
||||||
|
|
||||||
|
// We expect an error (cert file not found), but NOT a panic
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error due to invalid cert path, but got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomRoundTripper is a custom implementation of http.RoundTripper
|
||||||
|
type CustomRoundTripper struct{}
|
||||||
|
|
||||||
|
func (c *CustomRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
return http.DefaultTransport.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHttpWithTLS_CustomRoundTripper tests the edge case where DefaultHTTPClient has a custom RoundTripper
|
||||||
|
// that is NOT *http.Transport
|
||||||
|
func TestHttpWithTLS_CustomRoundTripper(t *testing.T) {
|
||||||
|
// Save original transport
|
||||||
|
originalTransport := DefaultHTTPClient.Transport
|
||||||
|
defer func() {
|
||||||
|
DefaultHTTPClient.Transport = originalTransport
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Set a custom RoundTripper that is NOT *http.Transport
|
||||||
|
customRoundTripper := &CustomRoundTripper{}
|
||||||
|
DefaultHTTPClient.Transport = customRoundTripper
|
||||||
|
|
||||||
|
// Create a recovery handler to catch potential panic
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
t.Errorf("httpWithTLS panicked with custom RoundTripper: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// This might panic if the code doesn't handle non-*http.Transport RoundTripper properly
|
||||||
|
_, _ = httpWithTLS("./testdata/invalid_cert.p12", "password")
|
||||||
|
}
|
||||||
@@ -176,6 +176,7 @@ type BatchGetExternalUserDetailsRequest struct {
|
|||||||
type ExternalUserDetailListResponse struct {
|
type ExternalUserDetailListResponse struct {
|
||||||
util.CommonError
|
util.CommonError
|
||||||
ExternalContactList []ExternalUserForBatch `json:"external_contact_list"`
|
ExternalContactList []ExternalUserForBatch `json:"external_contact_list"`
|
||||||
|
NextCursor string `json:"next_cursor"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExternalUserForBatch 批量获取外部联系人客户列表
|
// ExternalUserForBatch 批量获取外部联系人客户列表
|
||||||
@@ -214,23 +215,23 @@ type FollowInfo struct {
|
|||||||
|
|
||||||
// BatchGetExternalUserDetails 批量获取外部联系人详情
|
// BatchGetExternalUserDetails 批量获取外部联系人详情
|
||||||
// @see https://developer.work.weixin.qq.com/document/path/92994
|
// @see https://developer.work.weixin.qq.com/document/path/92994
|
||||||
func (r *Client) BatchGetExternalUserDetails(request BatchGetExternalUserDetailsRequest) ([]ExternalUserForBatch, error) {
|
func (r *Client) BatchGetExternalUserDetails(request BatchGetExternalUserDetailsRequest) ([]ExternalUserForBatch, string, error) {
|
||||||
accessToken, err := r.GetAccessToken()
|
accessToken, err := r.GetAccessToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
var response []byte
|
var response []byte
|
||||||
jsonData, err := json.Marshal(request)
|
jsonData, err := json.Marshal(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", fetchBatchExternalContactUserDetailURL, accessToken), string(jsonData))
|
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", fetchBatchExternalContactUserDetailURL, accessToken), string(jsonData))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
var result ExternalUserDetailListResponse
|
var result ExternalUserDetailListResponse
|
||||||
err = util.DecodeWithError(response, &result, "BatchGetExternalUserDetails")
|
err = util.DecodeWithError(response, &result, "BatchGetExternalUserDetails")
|
||||||
return result.ExternalContactList, err
|
return result.ExternalContactList, result.NextCursor, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUserRemarkRequest 修改客户备注信息请求体
|
// UpdateUserRemarkRequest 修改客户备注信息请求体
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import (
|
|||||||
|
|
||||||
// SignatureOptions 微信服务器验证参数
|
// SignatureOptions 微信服务器验证参数
|
||||||
type SignatureOptions struct {
|
type SignatureOptions struct {
|
||||||
Signature string `form:"msg_signature"`
|
Signature string `form:"msg_signature" json:"msg_signature"`
|
||||||
TimeStamp string `form:"timestamp"`
|
TimeStamp string `form:"timestamp" json:"timestamp"`
|
||||||
Nonce string `form:"nonce"`
|
Nonce string `form:"nonce" json:"nonce"`
|
||||||
EchoStr string `form:"echostr"`
|
EchoStr string `form:"echostr" json:"echostr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyURL 验证请求参数是否合法并返回解密后的消息内容
|
// VerifyURL 验证请求参数是否合法并返回解密后的消息内容
|
||||||
|
|||||||
@@ -191,12 +191,6 @@ func (r *Client) GetTempFile(mediaID string) ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查响应是否为错误信息
|
// 检查响应是否为错误信息,如果不是错误响应,则返回原始数据
|
||||||
err = util.DecodeWithCommonError(response, "GetTempFile")
|
return util.HandleFileResponse(response, "GetTempFile")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果不是错误响应,则返回原始数据
|
|
||||||
return response, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user