mirror of
https://github.com/silenceper/wechat.git
synced 2026-02-05 13:12:26 +08:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20a8183e88 | ||
|
|
f0f35e2f77 | ||
|
|
eebfb93386 | ||
|
|
9b028e5368 | ||
|
|
2bfc250f21 | ||
|
|
f0866babb5 | ||
|
|
7d93d1b9c8 | ||
|
|
78c00a9124 | ||
|
|
54f08cbd73 | ||
|
|
c34ff2031b | ||
|
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
## 作者公众号
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
58
cache/redis.go
vendored
58
cache/redis.go
vendored
@@ -17,14 +17,24 @@ type Redis struct {
|
|||||||
|
|
||||||
// RedisOpts redis 连接属性
|
// RedisOpts redis 连接属性
|
||||||
type RedisOpts struct {
|
type RedisOpts struct {
|
||||||
Host string `json:"host" yml:"host"`
|
Host string `json:"host" yaml:"host"`
|
||||||
Username string `json:"username" yaml:"username"`
|
Username string `json:"username" yaml:"username"`
|
||||||
Password string `json:"password" yml:"password"`
|
Password string `json:"password" yaml:"password"`
|
||||||
Database int `json:"database" yml:"database"`
|
Database int `json:"database" yaml:"database"`
|
||||||
MaxIdle int `json:"max_idle" yml:"max_idle"`
|
MinIdleConns int `json:"min_idle_conns" yaml:"min_idle_conns"` // 最小空闲连接数
|
||||||
MaxActive int `json:"max_active" yml:"max_active"`
|
PoolSize int `json:"pool_size" yaml:"pool_size"` // 连接池大小,0 表示使用默认值(即 CPU 核心数 * 10)
|
||||||
IdleTimeout int `json:"idle_timeout" yml:"idle_timeout"` // second
|
MaxRetries int `json:"max_retries" yaml:"max_retries"` // 最大重试次数,-1 表示不重试,0 表示使用默认值(即 3 次)
|
||||||
UseTLS bool `json:"use_tls" yml:"use_tls"` // 是否使用TLS
|
DialTimeout int `json:"dial_timeout" yaml:"dial_timeout"` // 连接超时时间(秒),0 表示使用默认值(即 5 秒)
|
||||||
|
ReadTimeout int `json:"read_timeout" yaml:"read_timeout"` // 读取超时时间(秒),-1 表示不超时,0 表示使用默认值(即 3 秒)
|
||||||
|
WriteTimeout int `json:"write_timeout" yaml:"write_timeout"` // 写入超时时间(秒),-1 表示不超时,0 表示使用默认值(即 ReadTimeout)
|
||||||
|
PoolTimeout int `json:"pool_timeout" yaml:"pool_timeout"` // 连接池获取连接超时时间(秒),0 表示使用默认值(即 ReadTimeout + 1 秒)
|
||||||
|
IdleTimeout int `json:"idle_timeout" yaml:"idle_timeout"` // 空闲连接超时时间(秒),-1 表示禁用空闲连接超时检查,0 表示使用默认值(即 5 分钟)
|
||||||
|
UseTLS bool `json:"use_tls" yaml:"use_tls"` // 是否使用 TLS
|
||||||
|
|
||||||
|
// Deprecated: 应使用 MinIdleConns 代替
|
||||||
|
MaxIdle int `json:"max_idle" yaml:"max_idle"`
|
||||||
|
// Deprecated: 应使用 PoolSize 代替
|
||||||
|
MaxActive int `json:"max_active" yaml:"max_active"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRedis 实例化
|
// NewRedis 实例化
|
||||||
@@ -34,10 +44,38 @@ func NewRedis(ctx context.Context, opts *RedisOpts) *Redis {
|
|||||||
DB: opts.Database,
|
DB: opts.Database,
|
||||||
Username: opts.Username,
|
Username: opts.Username,
|
||||||
Password: opts.Password,
|
Password: opts.Password,
|
||||||
IdleTimeout: time.Second * time.Duration(opts.IdleTimeout),
|
MinIdleConns: opts.MinIdleConns,
|
||||||
MinIdleConns: opts.MaxIdle,
|
PoolSize: opts.PoolSize,
|
||||||
|
MaxRetries: opts.MaxRetries,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 兼容旧的 MaxIdle 参数,仅在未显式设置 MinIdleConns 时生效
|
||||||
|
if opts.MaxIdle > 0 && opts.MinIdleConns == 0 {
|
||||||
|
uniOpt.MinIdleConns = opts.MaxIdle
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容旧的 MaxActive 参数,仅在未显式设置 PoolSize 时生效
|
||||||
|
if opts.MaxActive > 0 && opts.PoolSize == 0 {
|
||||||
|
uniOpt.PoolSize = opts.MaxActive
|
||||||
|
}
|
||||||
|
|
||||||
|
applyTimeout := func(seconds int, target *time.Duration) {
|
||||||
|
if seconds > 0 {
|
||||||
|
*target = time.Duration(seconds) * time.Second
|
||||||
|
} else if seconds == -1 {
|
||||||
|
// 当 seconds 为 -1 时,表示禁用超时:按 go-redis 约定,将超时时间设置为负值(如 -1ns)代表「无超时」
|
||||||
|
*target = -1
|
||||||
|
}
|
||||||
|
// 当 seconds 为 0 时,使用 go-redis 的默认超时配置:
|
||||||
|
// 不修改 target,保持其零值(0),由 go-redis 解释为“使用默认值”
|
||||||
|
}
|
||||||
|
|
||||||
|
applyTimeout(opts.DialTimeout, &uniOpt.DialTimeout)
|
||||||
|
applyTimeout(opts.ReadTimeout, &uniOpt.ReadTimeout)
|
||||||
|
applyTimeout(opts.WriteTimeout, &uniOpt.WriteTimeout)
|
||||||
|
applyTimeout(opts.PoolTimeout, &uniOpt.PoolTimeout)
|
||||||
|
applyTimeout(opts.IdleTimeout, &uniOpt.IdleTimeout)
|
||||||
|
|
||||||
if opts.UseTLS {
|
if opts.UseTLS {
|
||||||
h, _, err := net.SplitHostPort(opts.Host)
|
h, _, err := net.SplitHostPort(opts.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
268
cache/redis_test.go
vendored
268
cache/redis_test.go
vendored
@@ -6,6 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis/v2"
|
"github.com/alicebob/miniredis/v2"
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRedis(t *testing.T) {
|
func TestRedis(t *testing.T) {
|
||||||
@@ -18,7 +19,16 @@ func TestRedis(t *testing.T) {
|
|||||||
timeoutDuration = time.Second
|
timeoutDuration = time.Second
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
opts = &RedisOpts{
|
opts = &RedisOpts{
|
||||||
Host: server.Addr(),
|
Host: server.Addr(),
|
||||||
|
Password: "",
|
||||||
|
Database: 0,
|
||||||
|
PoolSize: 10,
|
||||||
|
MinIdleConns: 5,
|
||||||
|
DialTimeout: 5,
|
||||||
|
ReadTimeout: 5,
|
||||||
|
WriteTimeout: 5,
|
||||||
|
PoolTimeout: 5,
|
||||||
|
IdleTimeout: 300,
|
||||||
}
|
}
|
||||||
redis = NewRedis(ctx, opts)
|
redis = NewRedis(ctx, opts)
|
||||||
val = "silenceper"
|
val = "silenceper"
|
||||||
@@ -44,3 +54,259 @@ func TestRedis(t *testing.T) {
|
|||||||
t.Errorf("delete Error , err=%v", err)
|
t.Errorf("delete Error , err=%v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setupRedisServer 创建并返回一个 miniredis 服务器实例
|
||||||
|
func setupRedisServer(t *testing.T) *miniredis.Miniredis {
|
||||||
|
server, err := miniredis.Run()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("miniredis.Run Error", err)
|
||||||
|
}
|
||||||
|
t.Cleanup(server.Close)
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRedisMaxIdleMapping 测试只设置MaxIdle应该映射到MinIdleConns
|
||||||
|
func TestRedisMaxIdleMapping(t *testing.T) {
|
||||||
|
server := setupRedisServer(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
opts := &RedisOpts{
|
||||||
|
Host: server.Addr(),
|
||||||
|
Database: 0,
|
||||||
|
MaxIdle: 10,
|
||||||
|
}
|
||||||
|
r := NewRedis(ctx, opts)
|
||||||
|
|
||||||
|
// 获取底层的 UniversalClient 并断言为 *redis.Client
|
||||||
|
client, ok := r.conn.(*redis.Client)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("无法转换为 *redis.Client")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注意:MinIdleConns 表示期望的最小空闲连接数,但实际空闲连接数可能不同
|
||||||
|
// 我们需要通过 Options() 来验证配置是否正确应用
|
||||||
|
clientOpts := client.Options()
|
||||||
|
if clientOpts.MinIdleConns != 10 {
|
||||||
|
t.Errorf("期望 MinIdleConns = 10, 实际 = %d", clientOpts.MinIdleConns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRedisMaxActiveMapping 测试只设置MaxActive应该映射到PoolSize
|
||||||
|
func TestRedisMaxActiveMapping(t *testing.T) {
|
||||||
|
server := setupRedisServer(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
opts := &RedisOpts{
|
||||||
|
Host: server.Addr(),
|
||||||
|
Database: 0,
|
||||||
|
MaxActive: 20,
|
||||||
|
}
|
||||||
|
r := NewRedis(ctx, opts)
|
||||||
|
|
||||||
|
client, ok := r.conn.(*redis.Client)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("无法转换为 *redis.Client")
|
||||||
|
}
|
||||||
|
|
||||||
|
clientOpts := client.Options()
|
||||||
|
if clientOpts.PoolSize != 20 {
|
||||||
|
t.Errorf("期望 PoolSize = 20, 实际 = %d", clientOpts.PoolSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRedisNewFieldsPriority 测试新字段应该优先于旧字段
|
||||||
|
func TestRedisNewFieldsPriority(t *testing.T) {
|
||||||
|
server := setupRedisServer(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
opts := &RedisOpts{
|
||||||
|
Host: server.Addr(),
|
||||||
|
Database: 0,
|
||||||
|
MaxIdle: 5,
|
||||||
|
MinIdleConns: 15,
|
||||||
|
MaxActive: 10,
|
||||||
|
PoolSize: 30,
|
||||||
|
}
|
||||||
|
r := NewRedis(ctx, opts)
|
||||||
|
|
||||||
|
client, ok := r.conn.(*redis.Client)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("无法转换为 *redis.Client")
|
||||||
|
}
|
||||||
|
|
||||||
|
clientOpts := client.Options()
|
||||||
|
if clientOpts.MinIdleConns != 15 {
|
||||||
|
t.Errorf("期望 MinIdleConns = 15 (新字段优先), 实际 = %d", clientOpts.MinIdleConns)
|
||||||
|
}
|
||||||
|
if clientOpts.PoolSize != 30 {
|
||||||
|
t.Errorf("期望 PoolSize = 30 (新字段优先), 实际 = %d", clientOpts.PoolSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRedisPositiveTimeouts 测试正值超时应该正确应用
|
||||||
|
func TestRedisPositiveTimeouts(t *testing.T) {
|
||||||
|
server := setupRedisServer(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
opts := &RedisOpts{
|
||||||
|
Host: server.Addr(),
|
||||||
|
Database: 0,
|
||||||
|
DialTimeout: 10,
|
||||||
|
ReadTimeout: 20,
|
||||||
|
WriteTimeout: 30,
|
||||||
|
PoolTimeout: 40,
|
||||||
|
IdleTimeout: 50,
|
||||||
|
}
|
||||||
|
r := NewRedis(ctx, opts)
|
||||||
|
|
||||||
|
client, ok := r.conn.(*redis.Client)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("无法转换为 *redis.Client")
|
||||||
|
}
|
||||||
|
|
||||||
|
clientOpts := client.Options()
|
||||||
|
if clientOpts.DialTimeout != 10*time.Second {
|
||||||
|
t.Errorf("期望 DialTimeout = 10s, 实际 = %v", clientOpts.DialTimeout)
|
||||||
|
}
|
||||||
|
if clientOpts.ReadTimeout != 20*time.Second {
|
||||||
|
t.Errorf("期望 ReadTimeout = 20s, 实际 = %v", clientOpts.ReadTimeout)
|
||||||
|
}
|
||||||
|
if clientOpts.WriteTimeout != 30*time.Second {
|
||||||
|
t.Errorf("期望 WriteTimeout = 30s, 实际 = %v", clientOpts.WriteTimeout)
|
||||||
|
}
|
||||||
|
if clientOpts.PoolTimeout != 40*time.Second {
|
||||||
|
t.Errorf("期望 PoolTimeout = 40s, 实际 = %v", clientOpts.PoolTimeout)
|
||||||
|
}
|
||||||
|
if clientOpts.IdleTimeout != 50*time.Second {
|
||||||
|
t.Errorf("期望 IdleTimeout = 50s, 实际 = %v", clientOpts.IdleTimeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRedisNegativeTimeouts 测试-1值应该禁用超时
|
||||||
|
func TestRedisNegativeTimeouts(t *testing.T) {
|
||||||
|
server := setupRedisServer(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
opts := &RedisOpts{
|
||||||
|
Host: server.Addr(),
|
||||||
|
Database: 0,
|
||||||
|
DialTimeout: -1,
|
||||||
|
ReadTimeout: -1,
|
||||||
|
WriteTimeout: -1,
|
||||||
|
PoolTimeout: -1,
|
||||||
|
IdleTimeout: -1,
|
||||||
|
}
|
||||||
|
r := NewRedis(ctx, opts)
|
||||||
|
|
||||||
|
client, ok := r.conn.(*redis.Client)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("无法转换为 *redis.Client")
|
||||||
|
}
|
||||||
|
|
||||||
|
clientOpts := client.Options()
|
||||||
|
// -1 应该被设置为负值表示禁用超时
|
||||||
|
// DialTimeout, PoolTimeout, IdleTimeout 会被设置为 -1ns
|
||||||
|
if clientOpts.DialTimeout != -1 {
|
||||||
|
t.Errorf("期望 DialTimeout = -1ns (禁用), 实际 = %v", clientOpts.DialTimeout)
|
||||||
|
}
|
||||||
|
// ReadTimeout 和 WriteTimeout 在 go-redis 中有特殊处理
|
||||||
|
// 当设置为负值时,会被规范化为 0,这也表示无超时
|
||||||
|
t.Logf("ReadTimeout = %v (设置为-1后的值)", clientOpts.ReadTimeout)
|
||||||
|
t.Logf("WriteTimeout = %v (设置为-1后的值)", clientOpts.WriteTimeout)
|
||||||
|
|
||||||
|
if clientOpts.PoolTimeout != -1 {
|
||||||
|
t.Errorf("期望 PoolTimeout = -1ns (禁用), 实际 = %v", clientOpts.PoolTimeout)
|
||||||
|
}
|
||||||
|
if clientOpts.IdleTimeout != -1 {
|
||||||
|
t.Errorf("期望 IdleTimeout = -1ns (禁用), 实际 = %v", clientOpts.IdleTimeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRedisZeroTimeouts 测试0值应该使用go-redis默认值
|
||||||
|
func TestRedisZeroTimeouts(t *testing.T) {
|
||||||
|
server := setupRedisServer(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
opts := &RedisOpts{
|
||||||
|
Host: server.Addr(),
|
||||||
|
Database: 0,
|
||||||
|
DialTimeout: 0,
|
||||||
|
ReadTimeout: 0,
|
||||||
|
WriteTimeout: 0,
|
||||||
|
PoolTimeout: 0,
|
||||||
|
IdleTimeout: 0,
|
||||||
|
}
|
||||||
|
r := NewRedis(ctx, opts)
|
||||||
|
|
||||||
|
client, ok := r.conn.(*redis.Client)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("无法转换为 *redis.Client")
|
||||||
|
}
|
||||||
|
|
||||||
|
clientOpts := client.Options()
|
||||||
|
// 0值应该保持为0,由 go-redis 使用默认值
|
||||||
|
// go-redis 的默认值:
|
||||||
|
// DialTimeout: 5s
|
||||||
|
// ReadTimeout: 3s
|
||||||
|
// WriteTimeout: ReadTimeout
|
||||||
|
// PoolTimeout: ReadTimeout + 1s
|
||||||
|
// IdleTimeout: 5min
|
||||||
|
|
||||||
|
if clientOpts.DialTimeout == 0 {
|
||||||
|
t.Error("期望 DialTimeout 使用 go-redis 默认值 (5s), 实际为 0")
|
||||||
|
}
|
||||||
|
if clientOpts.ReadTimeout == 0 {
|
||||||
|
t.Error("期望 ReadTimeout 使用 go-redis 默认值 (3s), 实际为 0")
|
||||||
|
}
|
||||||
|
if clientOpts.WriteTimeout == 0 {
|
||||||
|
t.Error("期望 WriteTimeout 使用 go-redis 默认值 (ReadTimeout), 实际为 0")
|
||||||
|
}
|
||||||
|
if clientOpts.PoolTimeout == 0 {
|
||||||
|
t.Error("期望 PoolTimeout 使用 go-redis 默认值 (ReadTimeout + 1s), 实际为 0")
|
||||||
|
}
|
||||||
|
if clientOpts.IdleTimeout == 0 {
|
||||||
|
t.Error("期望 IdleTimeout 使用 go-redis 默认值 (5min), 实际为 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRedisMixedTimeouts 测试混合超时配置
|
||||||
|
func TestRedisMixedTimeouts(t *testing.T) {
|
||||||
|
server := setupRedisServer(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
opts := &RedisOpts{
|
||||||
|
Host: server.Addr(),
|
||||||
|
Database: 0,
|
||||||
|
DialTimeout: 5, // 正值
|
||||||
|
ReadTimeout: -1, // 禁用
|
||||||
|
WriteTimeout: 0, // 使用默认值
|
||||||
|
PoolTimeout: 10, // 正值
|
||||||
|
IdleTimeout: -1, // 禁用
|
||||||
|
}
|
||||||
|
r := NewRedis(ctx, opts)
|
||||||
|
|
||||||
|
client, ok := r.conn.(*redis.Client)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("无法转换为 *redis.Client")
|
||||||
|
}
|
||||||
|
|
||||||
|
clientOpts := client.Options()
|
||||||
|
if clientOpts.DialTimeout != 5*time.Second {
|
||||||
|
t.Errorf("期望 DialTimeout = 5s, 实际 = %v", clientOpts.DialTimeout)
|
||||||
|
}
|
||||||
|
// ReadTimeout 设置为 -1,会被 go-redis 处理为 0(无超时)
|
||||||
|
t.Logf("ReadTimeout = %v (设置为-1后的值)", clientOpts.ReadTimeout)
|
||||||
|
|
||||||
|
// WriteTimeout 设置为 0,应该使用 go-redis 的默认值
|
||||||
|
// 默认值通常是 ReadTimeout 的值
|
||||||
|
t.Logf("WriteTimeout = %v (设置为0后使用的默认值)", clientOpts.WriteTimeout)
|
||||||
|
|
||||||
|
if clientOpts.PoolTimeout != 10*time.Second {
|
||||||
|
t.Errorf("期望 PoolTimeout = 10s, 实际 = %v", clientOpts.PoolTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IdleTimeout 设置为 -1,应该被设置为 -1ns(禁用空闲超时)
|
||||||
|
if clientOpts.IdleTimeout != -1 {
|
||||||
|
t.Errorf("期望 IdleTimeout = -1ns (禁用), 实际 = %v", clientOpts.IdleTimeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
}
|
||||||
618
miniprogram/express/intracity.go
Normal file
618
miniprogram/express/intracity.go
Normal file
@@ -0,0 +1,618 @@
|
|||||||
|
package express
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/silenceper/wechat/v2/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 同城配送 API URL
|
||||||
|
// https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/intracity_service.html
|
||||||
|
const (
|
||||||
|
// 开通门店权限
|
||||||
|
intracityApplyURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/apply?access_token=%s"
|
||||||
|
// 创建门店
|
||||||
|
intracityCreateStoreURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/createstore?access_token=%s"
|
||||||
|
// 查询门店
|
||||||
|
intracityQueryStoreURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/querystore?access_token=%s"
|
||||||
|
// 更新门店
|
||||||
|
intracityUpdateStoreURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/updatestore?access_token=%s"
|
||||||
|
// 门店运费充值
|
||||||
|
intracityStoreChargeURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/storecharge?access_token=%s"
|
||||||
|
// 门店运费退款
|
||||||
|
intracityStoreRefundURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/storerefund?access_token=%s"
|
||||||
|
// 门店运费流水查询
|
||||||
|
intracityQueryFlowURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/queryflow?access_token=%s"
|
||||||
|
// 门店余额查询
|
||||||
|
intracityBalanceQueryURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/balancequery?access_token=%s"
|
||||||
|
// 预下配送单(查询运费)
|
||||||
|
intracityPreAddOrderURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/preaddorder?access_token=%s"
|
||||||
|
// 创建配送单
|
||||||
|
intracityAddOrderURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/addorder?access_token=%s"
|
||||||
|
// 查询配送单
|
||||||
|
intracityQueryOrderURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/queryorder?access_token=%s"
|
||||||
|
// 取消配送单
|
||||||
|
intracityCancelOrderURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/cancelorder?access_token=%s"
|
||||||
|
// 模拟配送回调(仅用于测试)
|
||||||
|
intracityMockNotifyURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/mocknotify?access_token=%s"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PayMode 充值/扣费主体
|
||||||
|
type PayMode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PayModeStore 门店
|
||||||
|
PayModeStore PayMode = "PAY_MODE_STORE"
|
||||||
|
// PayModeApp 小程序
|
||||||
|
PayModeApp PayMode = "PAY_MODE_APP"
|
||||||
|
// PayModeComponent 服务商
|
||||||
|
PayModeComponent PayMode = "PAY_MODE_COMPONENT"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OrderPattern 运力偏好
|
||||||
|
type OrderPattern uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
// OrderPatternPriceFirst 价格优先
|
||||||
|
OrderPatternPriceFirst OrderPattern = 1
|
||||||
|
// OrderPatternTransFirst 运力优先
|
||||||
|
OrderPatternTransFirst OrderPattern = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// FlowType 流水类型
|
||||||
|
type FlowType uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
// FlowTypeCharge 充值流水
|
||||||
|
FlowTypeCharge FlowType = 1
|
||||||
|
// FlowTypeConsume 消费流水
|
||||||
|
FlowTypeConsume FlowType = 2
|
||||||
|
// FlowTypeRefund 退款流水
|
||||||
|
FlowTypeRefund FlowType = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// IntracityDeliveryStatus 配送单状态
|
||||||
|
type IntracityDeliveryStatus int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
// IntracityDeliveryStatusReady 配送单待接单
|
||||||
|
IntracityDeliveryStatusReady IntracityDeliveryStatus = 100
|
||||||
|
// IntracityDeliveryStatusPickedUp 配送单待取货
|
||||||
|
IntracityDeliveryStatusPickedUp IntracityDeliveryStatus = 101
|
||||||
|
// IntracityDeliveryStatusOngoing 配送单配送中
|
||||||
|
IntracityDeliveryStatusOngoing IntracityDeliveryStatus = 102
|
||||||
|
// IntracityDeliveryStatusFinished 配送单已送达
|
||||||
|
IntracityDeliveryStatusFinished IntracityDeliveryStatus = 200
|
||||||
|
// IntracityDeliveryStatusCancelled 配送单已取消
|
||||||
|
IntracityDeliveryStatusCancelled IntracityDeliveryStatus = 300
|
||||||
|
// IntracityDeliveryStatusAbnormal 配送单异常
|
||||||
|
IntracityDeliveryStatusAbnormal IntracityDeliveryStatus = 400
|
||||||
|
)
|
||||||
|
|
||||||
|
// IntracityAddressInfo 门店地址信息
|
||||||
|
type IntracityAddressInfo struct {
|
||||||
|
Province string `json:"province"` // 省/自治区/直辖市
|
||||||
|
City string `json:"city"` // 地级市
|
||||||
|
Area string `json:"area"` // 县/县级市/区
|
||||||
|
Street string `json:"street"` // 街道
|
||||||
|
House string `json:"house"` // 具体门牌号或详细地址
|
||||||
|
Lat float64 `json:"lat"` // 门店所在地纬度
|
||||||
|
Lng float64 `json:"lng"` // 门店所在地经度
|
||||||
|
Phone string `json:"phone"` // 门店联系电话
|
||||||
|
Name string `json:"name,omitempty"` // 联系人姓名(收货地址时使用)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntracityStoreInfo 门店信息
|
||||||
|
type IntracityStoreInfo struct {
|
||||||
|
WxStoreID string `json:"wx_store_id"` // 微信门店编号
|
||||||
|
OutStoreID string `json:"out_store_id"` // 自定义门店编号
|
||||||
|
CityID string `json:"city_id"` // 门店所在城市ID
|
||||||
|
StoreName string `json:"store_name"` // 门店名称
|
||||||
|
OrderPattern OrderPattern `json:"order_pattern"` // 运力偏好
|
||||||
|
ServiceTransPrefer string `json:"service_trans_prefer"` // 优先使用的运力ID
|
||||||
|
AddressInfo IntracityAddressInfo `json:"address_info"` // 门店地址信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 门店管理接口 ============
|
||||||
|
|
||||||
|
// IntracityApply 开通门店权限
|
||||||
|
// https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/intracity_service.html
|
||||||
|
func (express *Express) IntracityApply(ctx context.Context) error {
|
||||||
|
accessToken, err := express.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(intracityApplyURL, accessToken)
|
||||||
|
response, err := util.PostJSONContext(ctx, uri, map[string]interface{}{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.DecodeWithCommonError(response, "IntracityApply")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateStoreRequest 创建门店请求参数
|
||||||
|
type CreateStoreRequest struct {
|
||||||
|
OutStoreID string `json:"out_store_id"` // 自定义门店编号
|
||||||
|
StoreName string `json:"store_name"` // 门店名称
|
||||||
|
OrderPattern OrderPattern `json:"order_pattern,omitempty"` // 运力偏好:1-价格优先,2-运力优先
|
||||||
|
ServiceTransPrefer string `json:"service_trans_prefer,omitempty"` // 优先使用的运力ID,order_pattern=2时必填
|
||||||
|
AddressInfo IntracityAddressInfo `json:"address_info"` // 门店地址信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateStoreResponse 创建门店返回参数
|
||||||
|
type CreateStoreResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
WxStoreID string `json:"wx_store_id"` // 微信门店编号
|
||||||
|
AppID string `json:"appid"` // 小程序appid
|
||||||
|
OutStoreID string `json:"out_store_id"` // 自定义门店ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntracityCreateStore 创建门店
|
||||||
|
func (express *Express) IntracityCreateStore(ctx context.Context, req *CreateStoreRequest) (res CreateStoreResponse, err error) {
|
||||||
|
accessToken, err := express.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(intracityCreateStoreURL, accessToken)
|
||||||
|
response, err := util.PostJSONContext(ctx, uri, req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.DecodeWithError(response, &res, "IntracityCreateStore")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryStoreRequest 查询门店请求参数
|
||||||
|
type QueryStoreRequest struct {
|
||||||
|
WxStoreID string `json:"wx_store_id,omitempty"` // 微信门店编号
|
||||||
|
OutStoreID string `json:"out_store_id,omitempty"` // 自定义门店编号
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryStoreResponse 查询门店返回参数
|
||||||
|
type QueryStoreResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
Total uint32 `json:"total"` // 符合条件的门店总数
|
||||||
|
AppID string `json:"appid"` // 小程序appid
|
||||||
|
StoreList []IntracityStoreInfo `json:"store_list"` // 门店信息列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntracityQueryStore 查询门店
|
||||||
|
func (express *Express) IntracityQueryStore(ctx context.Context, req *QueryStoreRequest) (res QueryStoreResponse, err error) {
|
||||||
|
accessToken, err := express.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(intracityQueryStoreURL, accessToken)
|
||||||
|
response, err := util.PostJSONContext(ctx, uri, req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.DecodeWithError(response, &res, "IntracityQueryStore")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStoreKeyInfo 更新门店的key信息
|
||||||
|
type UpdateStoreKeyInfo struct {
|
||||||
|
WxStoreID string `json:"wx_store_id,omitempty"` // 微信门店编号
|
||||||
|
OutStoreID string `json:"out_store_id,omitempty"` // 自定义门店编号,二选一
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStoreContent 更新门店的内容
|
||||||
|
type UpdateStoreContent struct {
|
||||||
|
StoreName string `json:"store_name,omitempty"` // 门店名称
|
||||||
|
OrderPattern OrderPattern `json:"order_pattern,omitempty"` // 运力偏好
|
||||||
|
ServiceTransPrefer string `json:"service_trans_prefer,omitempty"` // 优先使用的运力ID
|
||||||
|
AddressInfo *IntracityAddressInfo `json:"address_info,omitempty"` // 门店地址信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStoreRequest 更新门店请求参数
|
||||||
|
type UpdateStoreRequest struct {
|
||||||
|
Keys UpdateStoreKeyInfo `json:"keys"` // 门店编号
|
||||||
|
Content UpdateStoreContent `json:"content"` // 更新内容
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntracityUpdateStore 更新门店
|
||||||
|
func (express *Express) IntracityUpdateStore(ctx context.Context, req *UpdateStoreRequest) error {
|
||||||
|
accessToken, err := express.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(intracityUpdateStoreURL, accessToken)
|
||||||
|
response, err := util.PostJSONContext(ctx, uri, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.DecodeWithCommonError(response, "IntracityUpdateStore")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 充值退款接口 ============
|
||||||
|
|
||||||
|
// StoreChargeRequest 门店运费充值请求参数
|
||||||
|
type StoreChargeRequest struct {
|
||||||
|
WxStoreID string `json:"wx_store_id,omitempty"` // 微信门店编号,pay_mode=PAY_MODE_STORE时必传
|
||||||
|
ServiceTransID string `json:"service_trans_id"` // 运力ID
|
||||||
|
Amount uint32 `json:"amount"` // 充值金额,单位:分,50元起充
|
||||||
|
PayMode PayMode `json:"pay_mode,omitempty"` // 充值主体
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreChargeResponse 门店运费充值返回参数
|
||||||
|
type StoreChargeResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
PayURL string `json:"payurl"` // 充值页面地址
|
||||||
|
AppID string `json:"appid"` // 小程序appid
|
||||||
|
WxStoreID string `json:"wx_store_id"` // 微信门店编号
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntracityStoreCharge 门店运费充值
|
||||||
|
func (express *Express) IntracityStoreCharge(ctx context.Context, req *StoreChargeRequest) (res StoreChargeResponse, err error) {
|
||||||
|
accessToken, err := express.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(intracityStoreChargeURL, accessToken)
|
||||||
|
response, err := util.PostJSONContext(ctx, uri, req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.DecodeWithError(response, &res, "IntracityStoreCharge")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreRefundRequest 门店运费退款请求参数
|
||||||
|
type StoreRefundRequest struct {
|
||||||
|
WxStoreID string `json:"wx_store_id,omitempty"` // 微信门店编号
|
||||||
|
PayMode PayMode `json:"pay_mode,omitempty"` // 充值/扣费主体
|
||||||
|
ServiceTransID string `json:"service_trans_id"` // 运力ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreRefundResponse 门店运费退款返回参数
|
||||||
|
type StoreRefundResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
AppID string `json:"appid"` // 小程序appid
|
||||||
|
WxStoreID string `json:"wx_store_id"` // 微信门店编号
|
||||||
|
RefundAmount uint32 `json:"refund_amount"` // 退款金额,单位:分
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntracityStoreRefund 门店运费退款
|
||||||
|
func (express *Express) IntracityStoreRefund(ctx context.Context, req *StoreRefundRequest) (res StoreRefundResponse, err error) {
|
||||||
|
accessToken, err := express.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(intracityStoreRefundURL, accessToken)
|
||||||
|
response, err := util.PostJSONContext(ctx, uri, req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.DecodeWithError(response, &res, "IntracityStoreRefund")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryFlowRequest 门店运费流水查询请求参数
|
||||||
|
type QueryFlowRequest struct {
|
||||||
|
WxStoreID string `json:"wx_store_id"` // 微信门店编号
|
||||||
|
FlowType FlowType `json:"flow_type"` // 流水类型:1-充值,2-消费,3-退款
|
||||||
|
ServiceTransID string `json:"service_trans_id,omitempty"` // 运力ID
|
||||||
|
BeginTime uint32 `json:"begin_time,omitempty"` // 开始时间戳
|
||||||
|
EndTime uint32 `json:"end_time,omitempty"` // 结束时间戳
|
||||||
|
PayMode PayMode `json:"pay_mode"` // 扣费主体
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlowRecordInfo 流水记录信息
|
||||||
|
type FlowRecordInfo struct {
|
||||||
|
FlowType FlowType `json:"flow_type"` // 流水类型
|
||||||
|
AppID string `json:"appid"` // appid
|
||||||
|
WxStoreID string `json:"wx_store_id"` // 微信门店ID
|
||||||
|
PayOrderID uint64 `json:"pay_order_id,omitempty"` // 充值订单号
|
||||||
|
WxOrderID string `json:"wx_order_id,omitempty"` // 订单ID(消费流水)
|
||||||
|
ServiceTransID string `json:"service_trans_id"` // 运力ID
|
||||||
|
OpenID string `json:"openid,omitempty"` // 用户openid(消费流水)
|
||||||
|
DeliveryStatus int32 `json:"delivery_status,omitempty"` // 运单状态(消费流水)
|
||||||
|
PayAmount int32 `json:"pay_amount"` // 支付金额,单位:分
|
||||||
|
PayTime uint32 `json:"pay_time,omitempty"` // 支付时间
|
||||||
|
PayStatus string `json:"pay_status,omitempty"` // 支付状态
|
||||||
|
RefundStatus string `json:"refund_status,omitempty"` // 退款状态
|
||||||
|
RefundAmount int32 `json:"refund_amount,omitempty"` // 退款金额
|
||||||
|
RefundTime uint32 `json:"refund_time,omitempty"` // 退款时间
|
||||||
|
DeductAmount int32 `json:"deduct_amount,omitempty"` // 扣除违约金
|
||||||
|
CreateTime uint32 `json:"create_time"` // 创建时间
|
||||||
|
ConsumeDeadline uint32 `json:"consume_deadline,omitempty"` // 有效截止日期
|
||||||
|
BillID string `json:"bill_id,omitempty"` // 运单ID
|
||||||
|
DeliveryFinishedTime uint32 `json:"delivery_finished_time,omitempty"` // 运单完成配送的时间
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryFlowResponse 门店运费流水查询返回参数
|
||||||
|
type QueryFlowResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
Total uint32 `json:"total"` // 总数
|
||||||
|
FlowList []FlowRecordInfo `json:"flow_list"` // 流水数组
|
||||||
|
TotalPayAmt int `json:"total_pay_amt"` // 总支付金额
|
||||||
|
TotalRefundAmt int `json:"total_refund_amt"` // 总退款金额
|
||||||
|
TotalDeductAmt int `json:"total_deduct_amt"` // 总违约金(消费流水返回)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntracityQueryFlow 门店运费流水查询
|
||||||
|
func (express *Express) IntracityQueryFlow(ctx context.Context, req *QueryFlowRequest) (res QueryFlowResponse, err error) {
|
||||||
|
accessToken, err := express.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(intracityQueryFlowURL, accessToken)
|
||||||
|
response, err := util.PostJSONContext(ctx, uri, req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.DecodeWithError(response, &res, "IntracityQueryFlow")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// BalanceQueryRequest 门店余额查询请求参数
|
||||||
|
type BalanceQueryRequest struct {
|
||||||
|
WxStoreID string `json:"wx_store_id,omitempty"` // 微信门店编号
|
||||||
|
ServiceTransID string `json:"service_trans_id,omitempty"` // 运力ID
|
||||||
|
PayMode PayMode `json:"pay_mode,omitempty"` // 充值/扣费主体
|
||||||
|
}
|
||||||
|
|
||||||
|
// BalanceInfo 余额信息
|
||||||
|
type BalanceInfo struct {
|
||||||
|
ServiceTransID string `json:"service_trans_id"` // 运力ID
|
||||||
|
Balance int32 `json:"balance"` // 余额,单位:分
|
||||||
|
}
|
||||||
|
|
||||||
|
// BalanceQueryResponse 门店余额查询返回参数
|
||||||
|
type BalanceQueryResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
AppID string `json:"appid"` // 小程序appid
|
||||||
|
WxStoreID string `json:"wx_store_id"` // 微信门店编号
|
||||||
|
BalanceList []BalanceInfo `json:"balance_list"` // 余额列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntracityBalanceQuery 门店余额查询
|
||||||
|
func (express *Express) IntracityBalanceQuery(ctx context.Context, req *BalanceQueryRequest) (res BalanceQueryResponse, err error) {
|
||||||
|
accessToken, err := express.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(intracityBalanceQueryURL, accessToken)
|
||||||
|
response, err := util.PostJSONContext(ctx, uri, req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.DecodeWithError(response, &res, "IntracityBalanceQuery")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 配送订单接口 ============
|
||||||
|
|
||||||
|
// CargoInfo 货物信息
|
||||||
|
type CargoInfo struct {
|
||||||
|
Name string `json:"name"` // 货物名称
|
||||||
|
Num uint32 `json:"num,omitempty"` // 货物数量
|
||||||
|
Price uint32 `json:"price,omitempty"` // 货物价格,单位:分
|
||||||
|
Weight uint32 `json:"weight,omitempty"` // 货物重量,单位:克
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreAddOrderRequest 预下配送单请求参数
|
||||||
|
type PreAddOrderRequest struct {
|
||||||
|
WxStoreID string `json:"wx_store_id,omitempty"` // 微信门店编号,二选一
|
||||||
|
OutStoreID string `json:"out_store_id,omitempty"` // 自定义门店编号,二选一
|
||||||
|
UserOpenID string `json:"user_openid"` // 用户openid
|
||||||
|
UserPhone string `json:"user_phone,omitempty"` // 用户联系电话
|
||||||
|
UserName string `json:"user_name,omitempty"` // 用户姓名
|
||||||
|
UserLat float64 `json:"user_lat"` // 用户地址纬度
|
||||||
|
UserLng float64 `json:"user_lng"` // 用户地址经度
|
||||||
|
UserAddress string `json:"user_address"` // 用户详细地址
|
||||||
|
ServiceTransID string `json:"service_trans_id,omitempty"` // 运力ID,不传则查询所有运力
|
||||||
|
PayMode PayMode `json:"pay_mode,omitempty"` // 充值/扣费主体
|
||||||
|
CargoInfo *CargoInfo `json:"cargo_info,omitempty"` // 货物信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransInfo 运力信息
|
||||||
|
type TransInfo struct {
|
||||||
|
ServiceTransID string `json:"service_trans_id"` // 运力ID
|
||||||
|
ServiceName string `json:"service_name"` // 运力名称
|
||||||
|
Price uint32 `json:"price"` // 配送费用,单位:分
|
||||||
|
Distance uint32 `json:"distance"` // 配送距离,单位:米
|
||||||
|
Errcode int `json:"errcode"` // 错误码,0表示成功
|
||||||
|
Errmsg string `json:"errmsg"` // 错误信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreAddOrderResponse 预下配送单返回参数
|
||||||
|
type PreAddOrderResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
WxStoreID string `json:"wx_store_id"` // 微信门店编号
|
||||||
|
AppID string `json:"appid"` // 小程序appid
|
||||||
|
TransList []TransInfo `json:"trans_list"` // 运力列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntracityPreAddOrder 预下配送单(查询运费)
|
||||||
|
func (express *Express) IntracityPreAddOrder(ctx context.Context, req *PreAddOrderRequest) (res PreAddOrderResponse, err error) {
|
||||||
|
accessToken, err := express.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(intracityPreAddOrderURL, accessToken)
|
||||||
|
response, err := util.PostJSONContext(ctx, uri, req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.DecodeWithError(response, &res, "IntracityPreAddOrder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddOrderRequest 创建配送单请求参数
|
||||||
|
type AddOrderRequest struct {
|
||||||
|
WxStoreID string `json:"wx_store_id,omitempty"` // 微信门店编号,二选一
|
||||||
|
OutStoreID string `json:"out_store_id,omitempty"` // 自定义门店编号,二选一
|
||||||
|
OutOrderID string `json:"out_order_id"` // 自定义订单号,需唯一
|
||||||
|
ServiceTransID string `json:"service_trans_id,omitempty"` // 运力ID
|
||||||
|
UserOpenID string `json:"user_openid"` // 用户openid
|
||||||
|
UserPhone string `json:"user_phone"` // 用户联系电话
|
||||||
|
UserName string `json:"user_name"` // 用户姓名
|
||||||
|
UserLat float64 `json:"user_lat"` // 用户地址纬度
|
||||||
|
UserLng float64 `json:"user_lng"` // 用户地址经度
|
||||||
|
UserAddress string `json:"user_address"` // 用户详细地址
|
||||||
|
PayMode PayMode `json:"pay_mode,omitempty"` // 充值/扣费主体
|
||||||
|
CargoInfo *CargoInfo `json:"cargo_info,omitempty"` // 货物信息
|
||||||
|
OrderDetailPath string `json:"order_detail_path,omitempty"` // 订单详情页路径
|
||||||
|
CallbackURL string `json:"callback_url,omitempty"` // 配送状态回调URL
|
||||||
|
UseInsurance uint32 `json:"use_insurance,omitempty"` // 是否使用保价:0-不使用,1-使用
|
||||||
|
InsuranceValue uint32 `json:"insurance_value,omitempty"` // 保价金额,单位:分
|
||||||
|
ExpectTime uint32 `json:"expect_time,omitempty"` // 期望送达时间戳
|
||||||
|
Remark string `json:"remark,omitempty"` // 备注
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddOrderResponse 创建配送单返回参数
|
||||||
|
type AddOrderResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
WxOrderID string `json:"wx_order_id"` // 微信订单号
|
||||||
|
AppID string `json:"appid"` // 小程序appid
|
||||||
|
WxStoreID string `json:"wx_store_id"` // 微信门店编号
|
||||||
|
OutOrderID string `json:"out_order_id"` // 自定义订单号
|
||||||
|
ServiceTransID string `json:"service_trans_id"` // 运力ID
|
||||||
|
BillID string `json:"bill_id"` // 运力订单号
|
||||||
|
Price uint32 `json:"price"` // 配送费用,单位:分
|
||||||
|
Distance uint32 `json:"distance"` // 配送距离,单位:米
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntracityAddOrder 创建配送单
|
||||||
|
func (express *Express) IntracityAddOrder(ctx context.Context, req *AddOrderRequest) (res AddOrderResponse, err error) {
|
||||||
|
accessToken, err := express.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(intracityAddOrderURL, accessToken)
|
||||||
|
response, err := util.PostJSONContext(ctx, uri, req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.DecodeWithError(response, &res, "IntracityAddOrder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryOrderRequest 查询配送单请求参数
|
||||||
|
type QueryOrderRequest struct {
|
||||||
|
WxOrderID string `json:"wx_order_id,omitempty"` // 微信订单号,二选一
|
||||||
|
OutOrderID string `json:"out_order_id,omitempty"` // 自定义订单号,二选一
|
||||||
|
WxStoreID string `json:"wx_store_id,omitempty"` // 微信门店编号
|
||||||
|
OutStoreID string `json:"out_store_id,omitempty"` // 自定义门店编号
|
||||||
|
}
|
||||||
|
|
||||||
|
// RiderInfo 骑手信息
|
||||||
|
type RiderInfo struct {
|
||||||
|
Name string `json:"name"` // 骑手姓名
|
||||||
|
Phone string `json:"phone"` // 骑手电话
|
||||||
|
RiderCode string `json:"rider_code"` // 骑手编号
|
||||||
|
RiderImgURL string `json:"rider_img_url"` // 骑手头像URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryOrderResponse 查询配送单返回参数
|
||||||
|
type QueryOrderResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
WxOrderID string `json:"wx_order_id"` // 微信订单号
|
||||||
|
AppID string `json:"appid"` // 小程序appid
|
||||||
|
WxStoreID string `json:"wx_store_id"` // 微信门店编号
|
||||||
|
OutOrderID string `json:"out_order_id"` // 自定义订单号
|
||||||
|
ServiceTransID string `json:"service_trans_id"` // 运力ID
|
||||||
|
BillID string `json:"bill_id"` // 运力订单号
|
||||||
|
DeliveryStatus IntracityDeliveryStatus `json:"delivery_status"` // 配送状态
|
||||||
|
Price uint32 `json:"price"` // 配送费用,单位:分
|
||||||
|
Distance uint32 `json:"distance"` // 配送距离,单位:米
|
||||||
|
CreateTime uint32 `json:"create_time"` // 订单创建时间
|
||||||
|
RiderInfo *RiderInfo `json:"rider_info"` // 骑手信息
|
||||||
|
FinishTime uint32 `json:"finish_time"` // 订单完成时间
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntracityQueryOrder 查询配送单
|
||||||
|
func (express *Express) IntracityQueryOrder(ctx context.Context, req *QueryOrderRequest) (res QueryOrderResponse, err error) {
|
||||||
|
accessToken, err := express.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(intracityQueryOrderURL, accessToken)
|
||||||
|
response, err := util.PostJSONContext(ctx, uri, req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.DecodeWithError(response, &res, "IntracityQueryOrder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelOrderRequest 取消配送单请求参数
|
||||||
|
type CancelOrderRequest struct {
|
||||||
|
WxOrderID string `json:"wx_order_id,omitempty"` // 微信订单号,二选一
|
||||||
|
OutOrderID string `json:"out_order_id,omitempty"` // 自定义订单号,二选一
|
||||||
|
WxStoreID string `json:"wx_store_id,omitempty"` // 微信门店编号
|
||||||
|
OutStoreID string `json:"out_store_id,omitempty"` // 自定义门店编号
|
||||||
|
CancelReason string `json:"cancel_reason,omitempty"` // 取消原因
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelOrderResponse 取消配送单返回参数
|
||||||
|
type CancelOrderResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
WxOrderID string `json:"wx_order_id"` // 微信订单号
|
||||||
|
RefundAmount int32 `json:"refund_amount"` // 退款金额,单位:分
|
||||||
|
DeductAmount int32 `json:"deduct_amount"` // 扣除违约金,单位:分
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntracityCancelOrder 取消配送单
|
||||||
|
func (express *Express) IntracityCancelOrder(ctx context.Context, req *CancelOrderRequest) (res CancelOrderResponse, err error) {
|
||||||
|
accessToken, err := express.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(intracityCancelOrderURL, accessToken)
|
||||||
|
response, err := util.PostJSONContext(ctx, uri, req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.DecodeWithError(response, &res, "IntracityCancelOrder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockNotifyRequest 模拟配送回调请求参数(仅用于测试)
|
||||||
|
type MockNotifyRequest struct {
|
||||||
|
WxOrderID string `json:"wx_order_id"` // 微信订单号
|
||||||
|
DeliveryStatus IntracityDeliveryStatus `json:"delivery_status"` // 配送状态
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntracityMockNotify 模拟配送回调(仅用于测试)
|
||||||
|
func (express *Express) IntracityMockNotify(ctx context.Context, req *MockNotifyRequest) error {
|
||||||
|
accessToken, err := express.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf(intracityMockNotifyURL, accessToken)
|
||||||
|
response, err := util.PostJSONContext(ctx, uri, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.DecodeWithCommonError(response, "IntracityMockNotify")
|
||||||
|
}
|
||||||
@@ -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,11 @@ 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/ocr"
|
||||||
|
"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 +182,23 @@ 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOCR OCR接口
|
||||||
|
func (miniProgram *MiniProgram) GetOCR() *ocr.OCR {
|
||||||
|
return ocr.NewOCR(miniProgram.ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIntracity 同城配送接口
|
||||||
|
func (miniProgram *MiniProgram) GetIntracity() *express.Express {
|
||||||
|
return express.NewExpress(miniProgram.ctx)
|
||||||
|
}
|
||||||
|
|||||||
248
miniprogram/ocr/ocr.go
Normal file
248
miniprogram/ocr/ocr.go
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
package ocr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/silenceper/wechat/v2/miniprogram/context"
|
||||||
|
"github.com/silenceper/wechat/v2/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ocrIDCardURL = "https://api.weixin.qq.com/cv/ocr/idcard"
|
||||||
|
ocrBankCardURL = "https://api.weixin.qq.com/cv/ocr/bankcard"
|
||||||
|
ocrDrivingURL = "https://api.weixin.qq.com/cv/ocr/driving"
|
||||||
|
ocrDrivingLicenseURL = "https://api.weixin.qq.com/cv/ocr/drivinglicense"
|
||||||
|
ocrBizLicenseURL = "https://api.weixin.qq.com/cv/ocr/bizlicense"
|
||||||
|
ocrCommonURL = "https://api.weixin.qq.com/cv/ocr/comm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OCR struct
|
||||||
|
type OCR struct {
|
||||||
|
*context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// coordinate 坐标
|
||||||
|
type coordinate struct {
|
||||||
|
X int64 `json:"x,omitempty"`
|
||||||
|
Y int64 `json:"y,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// position 位置
|
||||||
|
type position struct {
|
||||||
|
LeftTop coordinate `json:"left_top"`
|
||||||
|
RightTop coordinate `json:"right_top"`
|
||||||
|
RightBottom coordinate `json:"right_bottom"`
|
||||||
|
LeftBottom coordinate `json:"left_bottom"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// imageSize 图片尺寸
|
||||||
|
type imageSize struct {
|
||||||
|
Width int64 `json:"w,omitempty"`
|
||||||
|
Height int64 `json:"h,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResDriving 行驶证返回结果
|
||||||
|
type ResDriving struct {
|
||||||
|
util.CommonError
|
||||||
|
|
||||||
|
PlateNumber string `json:"plate_num,omitempty"`
|
||||||
|
VehicleType string `json:"vehicle_type,omitempty"`
|
||||||
|
Owner string `json:"owner,omitempty"`
|
||||||
|
Address string `json:"addr,omitempty"`
|
||||||
|
UseCharacter string `json:"use_character,omitempty"`
|
||||||
|
Model string `json:"model,omitempty"`
|
||||||
|
Vin string `json:"vin,omitempty"`
|
||||||
|
EngineNumber string `json:"engine_num,omitempty"`
|
||||||
|
RegisterDate string `json:"register_date,omitempty"`
|
||||||
|
IssueDate string `json:"issue_date,omitempty"`
|
||||||
|
PlateNumberB string `json:"plate_num_b,omitempty"`
|
||||||
|
Record string `json:"record,omitempty"`
|
||||||
|
PassengersNumber string `json:"passengers_num,omitempty"`
|
||||||
|
TotalQuality string `json:"total_quality,omitempty"`
|
||||||
|
PrepareQuality string `json:"prepare_quality,omitempty"`
|
||||||
|
OverallSize string `json:"overall_size,omitempty"`
|
||||||
|
CardPositionFront map[string]position `json:"card_position_front,omitempty"`
|
||||||
|
CardPositionBack map[string]position `json:"card_position_back,omitempty"`
|
||||||
|
ImageSize imageSize `json:"img_size,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResIDCard 身份证返回结果
|
||||||
|
type ResIDCard struct {
|
||||||
|
util.CommonError
|
||||||
|
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Address string `json:"addr,omitempty"`
|
||||||
|
Gender string `json:"gender,omitempty"`
|
||||||
|
Nationality string `json:"nationality,omitempty"`
|
||||||
|
ValidDate string `json:"valid_date,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResBankCard 银行卡返回结果
|
||||||
|
type ResBankCard struct {
|
||||||
|
util.CommonError
|
||||||
|
|
||||||
|
Number string `json:"number,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResDrivingLicense 驾驶证返回结果
|
||||||
|
type ResDrivingLicense struct {
|
||||||
|
util.CommonError
|
||||||
|
|
||||||
|
IDNumber string `json:"id_num,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Sex string `json:"sex,omitempty"`
|
||||||
|
Nationality string `json:"nationality,omitempty"`
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
Birthday string `json:"birth_date,omitempty"`
|
||||||
|
IssueDate string `json:"issue_date,omitempty"`
|
||||||
|
CarClass string `json:"car_class,omitempty"`
|
||||||
|
ValidFrom string `json:"valid_from,omitempty"`
|
||||||
|
ValidTo string `json:"valid_to,omitempty"`
|
||||||
|
OfficialSeal string `json:"official_seal,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResBizLicense 营业执照返回结果
|
||||||
|
type ResBizLicense struct {
|
||||||
|
util.CommonError
|
||||||
|
|
||||||
|
RegisterNumber string `json:"reg_num,omitempty"`
|
||||||
|
Serial string `json:"serial,omitempty"`
|
||||||
|
LegalRepresentative string `json:"legal_representative,omitempty"`
|
||||||
|
EnterpriseName string `json:"enterprise_name,omitempty"`
|
||||||
|
TypeOfOrganization string `json:"type_of_organization,omitempty"`
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
TypeOfEnterprise string `json:"type_of_enterprise,omitempty"`
|
||||||
|
BusinessScope string `json:"business_scope,omitempty"`
|
||||||
|
RegisteredCapital string `json:"registered_capital,omitempty"`
|
||||||
|
PaidInCapital string `json:"paid_in_capital,omitempty"`
|
||||||
|
ValidPeriod string `json:"valid_period,omitempty"`
|
||||||
|
RegisterDate string `json:"registered_date,omitempty"`
|
||||||
|
CertPosition map[string]position `json:"cert_position,omitempty"`
|
||||||
|
ImageSize imageSize `json:"img_size,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResCommon 公共印刷品返回结果
|
||||||
|
type ResCommon struct {
|
||||||
|
util.CommonError
|
||||||
|
|
||||||
|
Items []commonItem `json:"items,omitempty"`
|
||||||
|
ImageSize imageSize `json:"img_size,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// commonItem 公共元素
|
||||||
|
type commonItem struct {
|
||||||
|
Position position `json:"pos"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOCR 实例
|
||||||
|
func NewOCR(c *context.Context) *OCR {
|
||||||
|
ocr := new(OCR)
|
||||||
|
ocr.Context = c
|
||||||
|
return ocr
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDCard 身份证OCR识别接口
|
||||||
|
func (ocr *OCR) IDCard(path string) (resIDCard ResIDCard, err error) {
|
||||||
|
accessToken, err := ocr.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrIDCardURL, url.QueryEscape(path), accessToken), "")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.DecodeWithError(response, &resIDCard, "OCRIDCard")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// BankCard 银行卡OCR识别接口
|
||||||
|
func (ocr *OCR) BankCard(path string) (resBankCard ResBankCard, err error) {
|
||||||
|
accessToken, err := ocr.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrBankCardURL, url.QueryEscape(path), accessToken), "")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.DecodeWithError(response, &resBankCard, "OCRBankCard")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Driving 行驶证OCR识别接口
|
||||||
|
func (ocr *OCR) Driving(path string) (resDriving ResDriving, err error) {
|
||||||
|
accessToken, err := ocr.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrDrivingURL, url.QueryEscape(path), accessToken), "")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.DecodeWithError(response, &resDriving, "OCRDriving")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrivingLicense 驾驶证OCR识别接口
|
||||||
|
func (ocr *OCR) DrivingLicense(path string) (resDrivingLicense ResDrivingLicense, err error) {
|
||||||
|
accessToken, err := ocr.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrDrivingLicenseURL, url.QueryEscape(path), accessToken), "")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.DecodeWithError(response, &resDrivingLicense, "OCRDrivingLicense")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// BizLicense 营业执照OCR识别接口
|
||||||
|
func (ocr *OCR) BizLicense(path string) (resBizLicense ResBizLicense, err error) {
|
||||||
|
accessToken, err := ocr.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrBizLicenseURL, url.QueryEscape(path), accessToken), "")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.DecodeWithError(response, &resBizLicense, "OCRBizLicense")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common 通用印刷体OCR识别接口
|
||||||
|
func (ocr *OCR) Common(path string) (resCommon ResCommon, err error) {
|
||||||
|
accessToken, err := ocr.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrCommonURL, url.QueryEscape(path), accessToken), "")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.DecodeWithError(response, &resCommon, "OCRCommon")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
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")
|
||||||
|
}
|
||||||
@@ -24,6 +24,18 @@ const (
|
|||||||
convertToOpenIDURL = "https://qyapi.weixin.qq.com/cgi-bin/user/convert_to_openid"
|
convertToOpenIDURL = "https://qyapi.weixin.qq.com/cgi-bin/user/convert_to_openid"
|
||||||
// convertToUserIDURL openID转userID
|
// convertToUserIDURL openID转userID
|
||||||
convertToUserIDURL = "https://qyapi.weixin.qq.com/cgi-bin/user/convert_to_userid"
|
convertToUserIDURL = "https://qyapi.weixin.qq.com/cgi-bin/user/convert_to_userid"
|
||||||
|
// userBatchDeleteURL 批量删除成员
|
||||||
|
userBatchDeleteURL = "https://qyapi.weixin.qq.com/cgi-bin/user/batchdelete?access_token=%s"
|
||||||
|
// userAuthSuccURL 登录二次验证
|
||||||
|
userAuthSuccURL = "https://qyapi.weixin.qq.com/cgi-bin/user/authsucc?access_token=%s&userid=%s"
|
||||||
|
// batchInviteURL 邀请成员
|
||||||
|
batchInviteURL = "https://qyapi.weixin.qq.com/cgi-bin/batch/invite?access_token=%s"
|
||||||
|
// getJoinQrcodeURL 获取加入企业二维码
|
||||||
|
getJoinQrcodeURL = "https://qyapi.weixin.qq.com/cgi-bin/corp/get_join_qrcode"
|
||||||
|
// getUseridURL 手机号获取userid
|
||||||
|
getUseridURL = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserid?access_token=%s"
|
||||||
|
// getUseridByEmailURL 邮箱获取userid
|
||||||
|
getUseridByEmailURL = "https://qyapi.weixin.qq.com/cgi-bin/user/get_userid_by_email?access_token=%s"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -158,18 +170,20 @@ func (r *Client) UserCreate(req *UserCreateRequest) (*UserCreateResponse, error)
|
|||||||
|
|
||||||
// UserUpdateRequest 更新成员请求
|
// UserUpdateRequest 更新成员请求
|
||||||
type UserUpdateRequest struct {
|
type UserUpdateRequest struct {
|
||||||
UserID string `json:"userid"`
|
UserID string `json:"userid"`
|
||||||
NewUserID string `json:"new_userid"`
|
NewUserID string `json:"new_userid"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Alias string `json:"alias"`
|
Alias string `json:"alias"`
|
||||||
Mobile string `json:"mobile"`
|
Mobile string `json:"mobile"`
|
||||||
Department []int `json:"department"`
|
Department []int `json:"department"`
|
||||||
Order []int `json:"order"`
|
Order []int `json:"order"`
|
||||||
Position string `json:"position"`
|
Position string `json:"position"`
|
||||||
Gender int `json:"gender"`
|
Gender int `json:"gender"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
BizMail string `json:"biz_mail"`
|
BizMail string `json:"biz_mail"`
|
||||||
BizMailAlias string `json:"biz_mail_alias"`
|
BizMailAlias struct {
|
||||||
|
Item []string `json:"item"`
|
||||||
|
} `json:"biz_mail_alias"`
|
||||||
IsLeaderInDept []int `json:"is_leader_in_dept"`
|
IsLeaderInDept []int `json:"is_leader_in_dept"`
|
||||||
DirectLeader []string `json:"direct_leader"`
|
DirectLeader []string `json:"direct_leader"`
|
||||||
Enable int `json:"enable"`
|
Enable int `json:"enable"`
|
||||||
@@ -445,3 +459,167 @@ func (r *Client) ConvertToUserID(openID string) (string, error) {
|
|||||||
err = util.DecodeWithError(response, result, "ConvertToUserID")
|
err = util.DecodeWithError(response, result, "ConvertToUserID")
|
||||||
return result.UserID, err
|
return result.UserID, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserBatchDeleteRequest 批量删除成员请求
|
||||||
|
type UserBatchDeleteRequest struct {
|
||||||
|
UseridList []string `json:"useridlist"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserBatchDelete 批量删除成员
|
||||||
|
// see https://developer.work.weixin.qq.com/document/path/90199
|
||||||
|
func (r *Client) UserBatchDelete(req *UserBatchDeleteRequest) error {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = r.GetAccessToken(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(fmt.Sprintf(userBatchDeleteURL, accessToken), req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return util.DecodeWithCommonError(response, "UserBatchDelete")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserAuthSucc 登录二次验证
|
||||||
|
// @see https://developer.work.weixin.qq.com/document/path/90203
|
||||||
|
func (r *Client) UserAuthSucc(userID string) error {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = r.GetAccessToken(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.HTTPGet(fmt.Sprintf(userAuthSuccURL, accessToken, userID)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return util.DecodeWithCommonError(response, "UserAuthSucc")
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchInviteRequest 邀请成员请求
|
||||||
|
type BatchInviteRequest struct {
|
||||||
|
User []string `json:"user"`
|
||||||
|
Party []int `json:"party"`
|
||||||
|
Tag []int `json:"tag"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchInviteResponse 邀请成员响应
|
||||||
|
type BatchInviteResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
InvalidUser []string `json:"invaliduser"`
|
||||||
|
InvalidParty []int `json:"invalidparty"`
|
||||||
|
InvalidTag []int `json:"invalidtag"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchInvite 邀请成员
|
||||||
|
// see https://developer.work.weixin.qq.com/document/path/90975
|
||||||
|
func (r *Client) BatchInvite(req *BatchInviteRequest) (*BatchInviteResponse, 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(batchInviteURL, accessToken), req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &BatchInviteResponse{}
|
||||||
|
err = util.DecodeWithError(response, result, "BatchInvite")
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetJoinQrcodeRequest 获取加入企业二维码请求
|
||||||
|
type GetJoinQrcodeRequest struct {
|
||||||
|
SizeType int `json:"size_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetJoinQrcodeResponse 获取加入企业二维码响应
|
||||||
|
type GetJoinQrcodeResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
JoinQrcode string `json:"join_qrcode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetJoinQrcode 获取加入企业二维码
|
||||||
|
// see https://developer.work.weixin.qq.com/document/path/91714
|
||||||
|
func (r *Client) GetJoinQrcode(req *GetJoinQrcodeRequest) (*GetJoinQrcodeResponse, error) {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
apiURL string
|
||||||
|
)
|
||||||
|
if accessToken, err = r.GetAccessToken(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if req.SizeType > 0 {
|
||||||
|
apiURL = fmt.Sprintf("%s?access_token=%s&size_type=%d", getJoinQrcodeURL, accessToken, req.SizeType)
|
||||||
|
} else {
|
||||||
|
apiURL = fmt.Sprintf("%s?access_token=%s", getJoinQrcodeURL, accessToken)
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.HTTPGet(apiURL); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &GetJoinQrcodeResponse{}
|
||||||
|
err = util.DecodeWithError(response, result, "GetJoinQrcode")
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUseridRequest 手机号获取userid请求
|
||||||
|
type GetUseridRequest struct {
|
||||||
|
Mobile string `json:"mobile"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUseridResponse 获取userid响应
|
||||||
|
type GetUseridResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
Userid string `json:"userid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserid 手机号获取userid
|
||||||
|
// see https://developer.work.weixin.qq.com/document/path/95402
|
||||||
|
func (r *Client) GetUserid(req *GetUseridRequest) (*GetUseridResponse, 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(getUseridURL, accessToken), req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &GetUseridResponse{}
|
||||||
|
err = util.DecodeWithError(response, result, "GetUserid")
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUseridByEmailRequest 邮箱获取userid请求
|
||||||
|
type GetUseridByEmailRequest struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
EmailType int `json:"email_type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUseridByEmail 邮箱获取userid
|
||||||
|
// see https://developer.work.weixin.qq.com/document/path/95895
|
||||||
|
func (r *Client) GetUseridByEmail(req *GetUseridByEmailRequest) (*GetUseridResponse, 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(getUseridByEmailURL, accessToken), req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &GetUseridResponse{}
|
||||||
|
err = util.DecodeWithError(response, result, "GetUseridByEmail")
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -158,24 +158,32 @@ type OptionGroupRuleCheckinDate struct {
|
|||||||
MaxAllowArriveEarly int64 `json:"max_allow_arrive_early"`
|
MaxAllowArriveEarly int64 `json:"max_allow_arrive_early"`
|
||||||
MaxAllowArriveLate int64 `json:"max_allow_arrive_late"`
|
MaxAllowArriveLate int64 `json:"max_allow_arrive_late"`
|
||||||
LateRule OptionGroupLateRule `json:"late_rule"`
|
LateRule OptionGroupLateRule `json:"late_rule"`
|
||||||
|
Biweekly OptionGroupBiweekly `json:"biweekly,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptionGroupRuleCheckinTime 工作日上下班打卡时间信息
|
// OptionGroupRuleCheckinTime 工作日上下班打卡时间信息
|
||||||
type OptionGroupRuleCheckinTime struct {
|
type OptionGroupRuleCheckinTime struct {
|
||||||
TimeID int64 `json:"time_id"`
|
TimeID int64 `json:"time_id"`
|
||||||
WorkSec int64 `json:"work_sec"`
|
WorkSec int64 `json:"work_sec"`
|
||||||
OffWorkSec int64 `json:"off_work_sec"`
|
OffWorkSec int64 `json:"off_work_sec"`
|
||||||
RemindWorkSec int64 `json:"remind_work_sec"`
|
RemindWorkSec int64 `json:"remind_work_sec"`
|
||||||
RemindOffWorkSec int64 `json:"remind_off_work_sec"`
|
RemindOffWorkSec int64 `json:"remind_off_work_sec"`
|
||||||
AllowRest bool `json:"allow_rest"`
|
AllowRest bool `json:"allow_rest"`
|
||||||
RestBeginTime int64 `json:"rest_begin_time"`
|
RestBeginTime int64 `json:"rest_begin_time"`
|
||||||
RestEndTime int64 `json:"rest_end_time"`
|
RestEndTime int64 `json:"rest_end_time"`
|
||||||
EarliestWorkSec int64 `json:"earliest_work_sec"`
|
EarliestWorkSec int64 `json:"earliest_work_sec"`
|
||||||
LatestWorkSec int64 `json:"latest_work_sec"`
|
LatestWorkSec int64 `json:"latest_work_sec"`
|
||||||
EarliestOffWorkSec int64 `json:"earliest_off_work_sec"`
|
EarliestOffWorkSec int64 `json:"earliest_off_work_sec"`
|
||||||
LatestOffWorkSec int64 `json:"latest_off_work_sec"`
|
LatestOffWorkSec int64 `json:"latest_off_work_sec"`
|
||||||
NoNeedCheckOn bool `json:"no_need_checkon"`
|
NoNeedCheckOn bool `json:"no_need_checkon"`
|
||||||
NoNeedCheckOff bool `json:"no_need_checkoff"`
|
NoNeedCheckOff bool `json:"no_need_checkoff"`
|
||||||
|
RestTimes []OptionGroupRuleRestTimes `json:"rest_times,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OptionGroupRuleRestTimes 多组休息时间
|
||||||
|
type OptionGroupRuleRestTimes struct {
|
||||||
|
RestBeginTime int64 `json:"rest_begin_time,omitempty"`
|
||||||
|
RestEndTime int64 `json:"rest_end_time,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptionGroupLateRule 晚走晚到时间规则信息
|
// OptionGroupLateRule 晚走晚到时间规则信息
|
||||||
@@ -192,6 +200,13 @@ type OptionGroupTimeRule struct {
|
|||||||
OnWorkFlexTime int64 `json:"onwork_flex_time"`
|
OnWorkFlexTime int64 `json:"onwork_flex_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OptionGroupBiweekly 大小周规则
|
||||||
|
type OptionGroupBiweekly struct {
|
||||||
|
EnableWeekdayRecurrence bool `json:"enable_weekday_recurrence"`
|
||||||
|
OddWorkdays []int64 `json:"odd_workdays"`
|
||||||
|
EvenWorkdays []int64 `json:"even_workdays"`
|
||||||
|
}
|
||||||
|
|
||||||
// OptionGroupSpeWorkdays 特殊工作日
|
// OptionGroupSpeWorkdays 特殊工作日
|
||||||
type OptionGroupSpeWorkdays struct {
|
type OptionGroupSpeWorkdays struct {
|
||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ type (
|
|||||||
ExpiresIn int `json:"expires_in"`
|
ExpiresIn int `json:"expires_in"`
|
||||||
ChatExpiresIn int `json:"chat_expires_in"`
|
ChatExpiresIn int `json:"chat_expires_in"`
|
||||||
UnionID string `json:"unionid"`
|
UnionID string `json:"unionid"`
|
||||||
|
IsExclusive bool `json:"is_exclusive"`
|
||||||
|
MarkSource bool `json:"mark_source"`
|
||||||
Conclusions ConclusionsRequest `json:"conclusions"`
|
Conclusions ConclusionsRequest `json:"conclusions"`
|
||||||
}
|
}
|
||||||
// AddContactWayResponse 配置客户联系「联系我」方式响应
|
// AddContactWayResponse 配置客户联系「联系我」方式响应
|
||||||
@@ -132,6 +134,7 @@ type (
|
|||||||
ExpiresIn int `json:"expires_in"`
|
ExpiresIn int `json:"expires_in"`
|
||||||
ChatExpiresIn int `json:"chat_expires_in"`
|
ChatExpiresIn int `json:"chat_expires_in"`
|
||||||
UnionID string `json:"unionid"`
|
UnionID string `json:"unionid"`
|
||||||
|
MarkSource bool `json:"mark_source"`
|
||||||
Conclusions ConclusionsResponse `json:"conclusions"`
|
Conclusions ConclusionsResponse `json:"conclusions"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -168,6 +171,7 @@ type (
|
|||||||
ExpiresIn int `json:"expires_in"`
|
ExpiresIn int `json:"expires_in"`
|
||||||
ChatExpiresIn int `json:"chat_expires_in"`
|
ChatExpiresIn int `json:"chat_expires_in"`
|
||||||
UnionID string `json:"unionid"`
|
UnionID string `json:"unionid"`
|
||||||
|
MarkSource bool `json:"mark_source"`
|
||||||
Conclusions ConclusionsRequest `json:"conclusions"`
|
Conclusions ConclusionsRequest `json:"conclusions"`
|
||||||
}
|
}
|
||||||
// UpdateContactWayResponse 更新企业已配置的「联系我」方式响应
|
// UpdateContactWayResponse 更新企业已配置的「联系我」方式响应
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ const (
|
|||||||
customerAcquisitionStatisticURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/customer_acquisition/statistic?access_token=%s"
|
customerAcquisitionStatisticURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/customer_acquisition/statistic?access_token=%s"
|
||||||
// customerAcquisitionGetChatInfo 获取成员多次收消息详情
|
// customerAcquisitionGetChatInfo 获取成员多次收消息详情
|
||||||
customerAcquisitionGetChatInfoURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/customer_acquisition/get_chat_info?access_token=%s"
|
customerAcquisitionGetChatInfoURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/customer_acquisition/get_chat_info?access_token=%s"
|
||||||
|
// customerAcquisitionGetPermitURL 获取客户可建联成员
|
||||||
|
customerAcquisitionGetPermitURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/customer_acquisition_app/get_permit?access_token=%s"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -68,9 +70,10 @@ type (
|
|||||||
// GetCustomerAcquisitionResponse 获取获客链接详情响应
|
// GetCustomerAcquisitionResponse 获取获客链接详情响应
|
||||||
GetCustomerAcquisitionResponse struct {
|
GetCustomerAcquisitionResponse struct {
|
||||||
util.CommonError
|
util.CommonError
|
||||||
Link Link `json:"link"`
|
Link Link `json:"link"`
|
||||||
Range CustomerAcquisitionRange `json:"range"`
|
Range CustomerAcquisitionRange `json:"range"`
|
||||||
SkipVerify bool `json:"skip_verify"`
|
PriorityOption CustomerPriorityOption `json:"priority_option"`
|
||||||
|
SkipVerify bool `json:"skip_verify"`
|
||||||
}
|
}
|
||||||
// Link 获客链接
|
// Link 获客链接
|
||||||
Link struct {
|
Link struct {
|
||||||
@@ -78,6 +81,8 @@ type (
|
|||||||
LinkName string `json:"link_name"`
|
LinkName string `json:"link_name"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
CreateTime int64 `json:"create_time"`
|
CreateTime int64 `json:"create_time"`
|
||||||
|
SkipVerify bool `json:"skip_verify"`
|
||||||
|
MarkSource bool `json:"mark_source"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomerAcquisitionRange 该获客链接使用范围
|
// CustomerAcquisitionRange 该获客链接使用范围
|
||||||
@@ -85,6 +90,12 @@ type (
|
|||||||
UserList []string `json:"user_list"`
|
UserList []string `json:"user_list"`
|
||||||
DepartmentList []int64 `json:"department_list"`
|
DepartmentList []int64 `json:"department_list"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CustomerPriorityOption 该获客链接的优先选项
|
||||||
|
CustomerPriorityOption struct {
|
||||||
|
PriorityType int `json:"priority_type"`
|
||||||
|
PriorityUseridList []string `json:"priority_userid_list"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetCustomerAcquisition 获客助手--获取获客链接详情
|
// GetCustomerAcquisition 获客助手--获取获客链接详情
|
||||||
@@ -109,9 +120,11 @@ func (r *Client) GetCustomerAcquisition(req *GetCustomerAcquisitionRequest) (*Ge
|
|||||||
type (
|
type (
|
||||||
// CreateCustomerAcquisitionLinkRequest 创建获客链接请求
|
// CreateCustomerAcquisitionLinkRequest 创建获客链接请求
|
||||||
CreateCustomerAcquisitionLinkRequest struct {
|
CreateCustomerAcquisitionLinkRequest struct {
|
||||||
LinkName string `json:"link_name"`
|
LinkName string `json:"link_name"`
|
||||||
Range CustomerAcquisitionRange `json:"range"`
|
Range CustomerAcquisitionRange `json:"range"`
|
||||||
SkipVerify bool `json:"skip_verify"`
|
SkipVerify bool `json:"skip_verify"`
|
||||||
|
PriorityOption CustomerPriorityOption `json:"priority_option"`
|
||||||
|
MarkSource bool `json:"mark_source"`
|
||||||
}
|
}
|
||||||
// CreateCustomerAcquisitionLinkResponse 创建获客链接响应
|
// CreateCustomerAcquisitionLinkResponse 创建获客链接响应
|
||||||
CreateCustomerAcquisitionLinkResponse struct {
|
CreateCustomerAcquisitionLinkResponse struct {
|
||||||
@@ -142,10 +155,12 @@ func (r *Client) CreateCustomerAcquisitionLink(req *CreateCustomerAcquisitionLin
|
|||||||
type (
|
type (
|
||||||
// UpdateCustomerAcquisitionLinkRequest 编辑获客链接请求
|
// UpdateCustomerAcquisitionLinkRequest 编辑获客链接请求
|
||||||
UpdateCustomerAcquisitionLinkRequest struct {
|
UpdateCustomerAcquisitionLinkRequest struct {
|
||||||
LinkID string `json:"link_id"`
|
LinkID string `json:"link_id"`
|
||||||
LinkName string `json:"link_name"`
|
LinkName string `json:"link_name"`
|
||||||
Range CustomerAcquisitionRange `json:"range"`
|
Range CustomerAcquisitionRange `json:"range"`
|
||||||
SkipVerify bool `json:"skip_verify"`
|
SkipVerify bool `json:"skip_verify"`
|
||||||
|
PriorityOption CustomerPriorityOption `json:"priority_option"`
|
||||||
|
MarkSource bool `json:"mark_source"`
|
||||||
}
|
}
|
||||||
// UpdateCustomerAcquisitionLinkResponse 编辑获客链接响应
|
// UpdateCustomerAcquisitionLinkResponse 编辑获客链接响应
|
||||||
UpdateCustomerAcquisitionLinkResponse struct {
|
UpdateCustomerAcquisitionLinkResponse struct {
|
||||||
@@ -349,3 +364,30 @@ func (r *Client) GetChatInfo(req *GetChatInfoRequest) (*GetChatInfoResponse, err
|
|||||||
err = util.DecodeWithError(response, result, "GetChatInfo")
|
err = util.DecodeWithError(response, result, "GetChatInfo")
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPermitResponse 获取客户可建联成员响应
|
||||||
|
type GetPermitResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
UserList []string `json:"user_list"`
|
||||||
|
DepartmentList []int `json:"department_list"`
|
||||||
|
TagList []int `json:"tag_list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPermit 获取客户可建联成员
|
||||||
|
// see https://developer.work.weixin.qq.com/document/path/101146
|
||||||
|
func (r *Client) GetPermit() (*GetPermitResponse, error) {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = r.GetAccessToken(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.HTTPGet(fmt.Sprintf(customerAcquisitionGetPermitURL, accessToken)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &GetPermitResponse{}
|
||||||
|
err = util.DecodeWithError(response, result, "CustomerAcquisitionGetPermit")
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 修改客户备注信息请求体
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ type (
|
|||||||
RoomBaseID int `json:"room_base_id"` //非必填 自动建群的群起始序号,当auto_create_room为1时有效
|
RoomBaseID int `json:"room_base_id"` //非必填 自动建群的群起始序号,当auto_create_room为1时有效
|
||||||
ChatIDList []string `json:"chat_id_list"` //必填 使用该配置的客户群ID列表,支持5个。见客户群ID获取方法
|
ChatIDList []string `json:"chat_id_list"` //必填 使用该配置的客户群ID列表,支持5个。见客户群ID获取方法
|
||||||
State string `json:"state"` //非必填 企业自定义的state参数,用于区分不同的入群渠道。不超过30个UTF-8字符
|
State string `json:"state"` //非必填 企业自定义的state参数,用于区分不同的入群渠道。不超过30个UTF-8字符
|
||||||
|
MarkSource bool `json:"mark_source"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddJoinWayResponse 添加群配置返回值
|
// AddJoinWayResponse 添加群配置返回值
|
||||||
@@ -65,6 +66,7 @@ type (
|
|||||||
ChatIDList []string `json:"chat_id_list"`
|
ChatIDList []string `json:"chat_id_list"`
|
||||||
QrCode string `json:"qr_code"`
|
QrCode string `json:"qr_code"`
|
||||||
State string `json:"state"`
|
State string `json:"state"`
|
||||||
|
MarkSource bool `json:"mark_source"`
|
||||||
}
|
}
|
||||||
//GetJoinWayResponse 获取群配置的返回值
|
//GetJoinWayResponse 获取群配置的返回值
|
||||||
GetJoinWayResponse struct {
|
GetJoinWayResponse struct {
|
||||||
@@ -103,6 +105,7 @@ type UpdateJoinWayRequest struct {
|
|||||||
RoomBaseID int `json:"room_base_id"` //非必填 自动建群的群起始序号,当auto_create_room为1时有效
|
RoomBaseID int `json:"room_base_id"` //非必填 自动建群的群起始序号,当auto_create_room为1时有效
|
||||||
ChatIDList []string `json:"chat_id_list"` //必填 使用该配置的客户群ID列表,支持5个。见客户群ID获取方法
|
ChatIDList []string `json:"chat_id_list"` //必填 使用该配置的客户群ID列表,支持5个。见客户群ID获取方法
|
||||||
State string `json:"state"` //非必填 企业自定义的state参数,用于区分不同的入群渠道。不超过30个UTF-8字符
|
State string `json:"state"` //非必填 企业自定义的state参数,用于区分不同的入群渠道。不超过30个UTF-8字符
|
||||||
|
MarkSource bool `json:"mark_source"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateJoinWay 更新客户群进群方式配置
|
// UpdateJoinWay 更新客户群进群方式配置
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ var (
|
|||||||
getUserInfoURL = "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=%s&code=%s"
|
getUserInfoURL = "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=%s&code=%s"
|
||||||
// getUserDetailURL 获取访问用户敏感信息
|
// getUserDetailURL 获取访问用户敏感信息
|
||||||
getUserDetailURL = "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserdetail?access_token=%s"
|
getUserDetailURL = "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserdetail?access_token=%s"
|
||||||
|
// getTfaInfoURL 获取用户二次验证信息
|
||||||
|
getTfaInfoURL = "https://qyapi.weixin.qq.com/cgi-bin/auth/get_tfa_info?access_token=%s"
|
||||||
|
// tfaSuccURL 使用二次验证
|
||||||
|
tfaSuccURL = "https://qyapi.weixin.qq.com/cgi-bin/user/tfa_succ?access_token=%s"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewOauth new init oauth
|
// NewOauth new init oauth
|
||||||
@@ -163,3 +167,57 @@ func (ctr *Oauth) GetUserDetail(req *GetUserDetailRequest) (*GetUserDetailRespon
|
|||||||
err = util.DecodeWithError(response, result, "GetUserDetail")
|
err = util.DecodeWithError(response, result, "GetUserDetail")
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTfaInfoRequest 获取用户二次验证信息请求
|
||||||
|
type GetTfaInfoRequest struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTfaInfoResponse 获取用户二次验证信息响应
|
||||||
|
type GetTfaInfoResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
UserID string `json:"userid"`
|
||||||
|
TfaCode string `json:"tfa_code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTfaInfo 获取用户二次验证信息
|
||||||
|
// @see https://developer.work.weixin.qq.com/document/path/99499
|
||||||
|
func (ctr *Oauth) GetTfaInfo(req *GetTfaInfoRequest) (*GetTfaInfoResponse, error) {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = ctr.GetAccessToken(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(fmt.Sprintf(getTfaInfoURL, accessToken), req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &GetTfaInfoResponse{}
|
||||||
|
err = util.DecodeWithError(response, result, "GetTfaInfo")
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TfaSuccRequest 使用二次验证请求
|
||||||
|
type TfaSuccRequest struct {
|
||||||
|
UserID string `json:"userid"`
|
||||||
|
TfaCode string `json:"tfa_code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TfaSucc 使用二次验证
|
||||||
|
// @see https://developer.work.weixin.qq.com/document/path/99500
|
||||||
|
func (ctr *Oauth) TfaSucc(req *TfaSuccRequest) error {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = ctr.GetAccessToken(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(fmt.Sprintf(tfaSuccURL, accessToken), req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return util.DecodeWithCommonError(response, "TfaSucc")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user