mirror of
https://github.com/silenceper/wechat.git
synced 2026-02-05 13:12:26 +08:00
Compare commits
42 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 | ||
|
|
8a810837a4 | ||
|
|
c51d41ee8a | ||
|
|
24f812d187 | ||
|
|
dd43b7baa3 | ||
|
|
2dfd2ff608 | ||
|
|
23bb10b0c9 | ||
|
|
b639d2235d | ||
|
|
26d2093bd7 | ||
|
|
cf42cd8d54 | ||
|
|
85ee45580b | ||
|
|
208d5c528a | ||
|
|
b5f9a8933e | ||
|
|
52fb5596d3 | ||
|
|
44150c557e | ||
|
|
635a0c640d |
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>
|
||||||
|
|
||||||
|
|
||||||
|
## 作者公众号
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
75
cache/redis.go
vendored
75
cache/redis.go
vendored
@@ -2,6 +2,8 @@ package cache
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
@@ -15,25 +17,76 @@ type Redis struct {
|
|||||||
|
|
||||||
// RedisOpts redis 连接属性
|
// RedisOpts redis 连接属性
|
||||||
type RedisOpts struct {
|
type RedisOpts struct {
|
||||||
Host string `yml:"host" json:"host"`
|
Host string `json:"host" yaml:"host"`
|
||||||
Username string `yaml:"username" json:"username"`
|
Username string `json:"username" yaml:"username"`
|
||||||
Password string `yml:"password" json:"password"`
|
Password string `json:"password" yaml:"password"`
|
||||||
Database int `yml:"database" json:"database"`
|
Database int `json:"database" yaml:"database"`
|
||||||
MaxIdle int `yml:"max_idle" json:"max_idle"`
|
MinIdleConns int `json:"min_idle_conns" yaml:"min_idle_conns"` // 最小空闲连接数
|
||||||
MaxActive int `yml:"max_active" json:"max_active"`
|
PoolSize int `json:"pool_size" yaml:"pool_size"` // 连接池大小,0 表示使用默认值(即 CPU 核心数 * 10)
|
||||||
IdleTimeout int `yml:"idle_timeout" json:"idle_timeout"` // second
|
MaxRetries int `json:"max_retries" yaml:"max_retries"` // 最大重试次数,-1 表示不重试,0 表示使用默认值(即 3 次)
|
||||||
|
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 实例化
|
||||||
func NewRedis(ctx context.Context, opts *RedisOpts) *Redis {
|
func NewRedis(ctx context.Context, opts *RedisOpts) *Redis {
|
||||||
conn := redis.NewUniversalClient(&redis.UniversalOptions{
|
uniOpt := &redis.UniversalOptions{
|
||||||
Addrs: []string{opts.Host},
|
Addrs: []string{opts.Host},
|
||||||
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 {
|
||||||
|
h, _, err := net.SplitHostPort(opts.Host)
|
||||||
|
if err != nil {
|
||||||
|
h = opts.Host
|
||||||
|
}
|
||||||
|
uniOpt.TLSConfig = &tls.Config{
|
||||||
|
ServerName: h,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := redis.NewUniversalClient(uniOpt)
|
||||||
return &Redis{ctx: ctx, conn: conn}
|
return &Redis{ctx: ctx, conn: conn}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package credential
|
package credential
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
context2 "context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -42,6 +43,16 @@ type ResTicket struct {
|
|||||||
|
|
||||||
// GetTicket 获取jsapi_ticket
|
// GetTicket 获取jsapi_ticket
|
||||||
func (js *DefaultJsTicket) GetTicket(accessToken string) (ticketStr string, err error) {
|
func (js *DefaultJsTicket) GetTicket(accessToken string) (ticketStr string, err error) {
|
||||||
|
return js.GetTicketContext(context2.Background(), accessToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTicketFromServer 从服务器中获取ticket
|
||||||
|
func GetTicketFromServer(accessToken string) (ticket ResTicket, err error) {
|
||||||
|
return GetTicketFromServerContext(context2.Background(), accessToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTicketContext 获取jsapi_ticket
|
||||||
|
func (js *DefaultJsTicket) GetTicketContext(ctx context2.Context, accessToken string) (ticketStr string, err error) {
|
||||||
// 先从cache中取
|
// 先从cache中取
|
||||||
jsAPITicketCacheKey := fmt.Sprintf("%s_jsapi_ticket_%s", js.cacheKeyPrefix, js.appID)
|
jsAPITicketCacheKey := fmt.Sprintf("%s_jsapi_ticket_%s", js.cacheKeyPrefix, js.appID)
|
||||||
if val := js.cache.Get(jsAPITicketCacheKey); val != nil {
|
if val := js.cache.Get(jsAPITicketCacheKey); val != nil {
|
||||||
@@ -57,7 +68,7 @@ func (js *DefaultJsTicket) GetTicket(accessToken string) (ticketStr string, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ticket ResTicket
|
var ticket ResTicket
|
||||||
ticket, err = GetTicketFromServer(accessToken)
|
ticket, err = GetTicketFromServerContext(ctx, accessToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -67,11 +78,11 @@ func (js *DefaultJsTicket) GetTicket(accessToken string) (ticketStr string, err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTicketFromServer 从服务器中获取ticket
|
// GetTicketFromServerContext 从服务器中获取ticket
|
||||||
func GetTicketFromServer(accessToken string) (ticket ResTicket, err error) {
|
func GetTicketFromServerContext(ctx context2.Context, accessToken string) (ticket ResTicket, err error) {
|
||||||
var response []byte
|
var response []byte
|
||||||
url := fmt.Sprintf(getTicketURL, accessToken)
|
url := fmt.Sprintf(getTicketURL, accessToken)
|
||||||
response, err = util.HTTPGet(url)
|
response, err = util.HTTPGetContext(ctx, url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
22
credential/default_js_ticket_test.go
Normal file
22
credential/default_js_ticket_test.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package credential
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"gopkg.in/h2non/gock.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestGetTicketFromServerContext 测试 GetTicketFromServerContext 函数
|
||||||
|
func TestGetTicketFromServerContext(t *testing.T) {
|
||||||
|
defer gock.Off()
|
||||||
|
gock.New(fmt.Sprintf(getTicketURL, "arg-ak")).Reply(200).JSON(&ResTicket{Ticket: "mock-ticket", ExpiresIn: 10})
|
||||||
|
|
||||||
|
ticket, err := GetTicketFromServerContext(context.Background(), "arg-ak")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, int64(0), ticket.ErrCode)
|
||||||
|
assert.Equal(t, "mock-ticket", ticket.Ticket, "they should be equal")
|
||||||
|
assert.Equal(t, int64(10), ticket.ExpiresIn, "they should be equal")
|
||||||
|
}
|
||||||
@@ -1,7 +1,15 @@
|
|||||||
package credential
|
package credential
|
||||||
|
|
||||||
|
import context2 "context"
|
||||||
|
|
||||||
// JsTicketHandle js ticket获取
|
// JsTicketHandle js ticket获取
|
||||||
type JsTicketHandle interface {
|
type JsTicketHandle interface {
|
||||||
// GetTicket 获取ticket
|
// GetTicket 获取ticket
|
||||||
GetTicket(accessToken string) (ticket string, err error)
|
GetTicket(accessToken string) (ticket string, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JsTicketContextHandle js ticket获取
|
||||||
|
type JsTicketContextHandle interface {
|
||||||
|
JsTicketHandle
|
||||||
|
GetTicketContext(ctx context2.Context, accessToken string) (ticket string, err error)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,11 +10,22 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// code2SessionURL 小程序登录
|
||||||
code2SessionURL = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code"
|
code2SessionURL = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code"
|
||||||
|
// checkEncryptedDataURL 检查加密信息
|
||||||
checkEncryptedDataURL = "https://api.weixin.qq.com/wxa/business/checkencryptedmsg?access_token=%s"
|
checkEncryptedDataURL = "https://api.weixin.qq.com/wxa/business/checkencryptedmsg?access_token=%s"
|
||||||
|
// getPhoneNumber 获取手机号
|
||||||
getPhoneNumber = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s"
|
getPhoneNumber = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s"
|
||||||
|
// checkSessionURL 检验登录态
|
||||||
|
checkSessionURL = "https://api.weixin.qq.com/wxa/checksession?access_token=%s&signature=%s&openid=%s&sig_method=hmac_sha256"
|
||||||
|
// resetUserSessionKeyURL 重置登录态
|
||||||
|
resetUserSessionKeyURL = "https://api.weixin.qq.com/wxa/resetusersessionkey?access_token=%s&signature=%s&openid=%s&sig_method=hmac_sha256"
|
||||||
|
// getPluginOpenPIDURL 获取插件用户openPID
|
||||||
|
getPluginOpenPIDURL = "https://api.weixin.qq.com/wxa/getpluginopenpid?access_token=%s"
|
||||||
|
// getPaidUnionIDURL 支付后获取 UnionID
|
||||||
|
getPaidUnionIDURL = "https://api.weixin.qq.com/wxa/getpaidunionid"
|
||||||
|
// getUserEncryptKeyURL 获取用户encryptKey
|
||||||
|
getUserEncryptKeyURL = "https://api.weixin.qq.com/wxa/business/getuserencryptkey?access_token=%s&signature=%s&openid=%s&sig_method=hmac_sha256"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Auth 登录/用户信息
|
// Auth 登录/用户信息
|
||||||
@@ -65,9 +76,45 @@ func (auth *Auth) Code2SessionContext(ctx context2.Context, jsCode string) (resu
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// GetPaidUnionIDRequest 支付后获取UnionID请求
|
||||||
|
GetPaidUnionIDRequest struct {
|
||||||
|
OpenID string `json:"openid"`
|
||||||
|
TransactionID string `json:"transaction_id,omitempty"`
|
||||||
|
MchID string `json:"mch_id,omitempty"`
|
||||||
|
OutTradeNo string `json:"out_trade_no,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPaidUnionIDResponse 支付后获取UnionID响应
|
||||||
|
GetPaidUnionIDResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
UnionID string `json:"unionid"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// GetPaidUnionID 用户支付完成后,获取该用户的 UnionId,无需用户授权
|
// GetPaidUnionID 用户支付完成后,获取该用户的 UnionId,无需用户授权
|
||||||
func (auth *Auth) GetPaidUnionID() {
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/basic-info/getPaidUnionid.html
|
||||||
// TODO
|
func (auth *Auth) GetPaidUnionID(req *GetPaidUnionIDRequest) (string, error) {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = auth.GetAccessToken(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var url string
|
||||||
|
if req.TransactionID != "" {
|
||||||
|
url = fmt.Sprintf("%s?access_token=%s&openid=%s&transaction_id=%s", getPaidUnionIDURL, accessToken, req.OpenID, req.TransactionID)
|
||||||
|
} else {
|
||||||
|
url = fmt.Sprintf("%s?access_token=%s&openid=%s&mch_id=%s&out_trade_no=%s", getPaidUnionIDURL, accessToken, req.OpenID, req.MchID, req.OutTradeNo)
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.HTTPGet(url); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
result := &GetPaidUnionIDResponse{}
|
||||||
|
err = util.DecodeWithError(response, result, "GetPaidUnionID")
|
||||||
|
return result.UnionID, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckEncryptedData .检查加密信息是否由微信生成(当前只支持手机号加密数据),只能检测最近3天生成的加密数据
|
// CheckEncryptedData .检查加密信息是否由微信生成(当前只支持手机号加密数据),只能检测最近3天生成的加密数据
|
||||||
@@ -81,7 +128,7 @@ func (auth *Auth) CheckEncryptedDataContext(ctx context2.Context, encryptedMsgHa
|
|||||||
var (
|
var (
|
||||||
at string
|
at string
|
||||||
)
|
)
|
||||||
if at, err = auth.GetAccessToken(); err != nil {
|
if at, err = auth.GetAccessTokenContext(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +167,7 @@ func (auth *Auth) GetPhoneNumberContext(ctx context2.Context, code string) (*Get
|
|||||||
at string
|
at string
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if at, err = auth.GetAccessToken(); err != nil {
|
if at, err = auth.GetAccessTokenContext(ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
body := map[string]interface{}{
|
body := map[string]interface{}{
|
||||||
@@ -146,3 +193,115 @@ func (auth *Auth) GetPhoneNumberContext(ctx context2.Context, code string) (*Get
|
|||||||
func (auth *Auth) GetPhoneNumber(code string) (*GetPhoneNumberResponse, error) {
|
func (auth *Auth) GetPhoneNumber(code string) (*GetPhoneNumberResponse, error) {
|
||||||
return auth.GetPhoneNumberContext(context2.Background(), code)
|
return auth.GetPhoneNumberContext(context2.Background(), code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckSession 检验登录态
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/checkSessionKey.html
|
||||||
|
func (auth *Auth) CheckSession(signature, openID string) error {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = auth.GetAccessToken(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.HTTPGet(fmt.Sprintf(checkSessionURL, accessToken, signature, openID)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return util.DecodeWithCommonError(response, "CheckSession")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetUserSessionKeyResponse 重置登录态响应
|
||||||
|
type ResetUserSessionKeyResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
OpenID string `json:"openid"`
|
||||||
|
SessionKey string `json:"session_key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetUserSessionKey 重置登录态
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/ResetUserSessionKey.html
|
||||||
|
func (auth *Auth) ResetUserSessionKey(signature, openID string) (*ResetUserSessionKeyResponse, error) {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = auth.GetAccessToken(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.HTTPGet(fmt.Sprintf(resetUserSessionKeyURL, accessToken, signature, openID)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &ResetUserSessionKeyResponse{}
|
||||||
|
err = util.DecodeWithError(response, result, "ResetUserSessionKey")
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// GetPluginOpenPIDRequest 获取插件用户openPID请求
|
||||||
|
GetPluginOpenPIDRequest struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPluginOpenPIDResponse 获取插件用户openPID响应
|
||||||
|
GetPluginOpenPIDResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
OpenPID string `json:"openpid"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetPluginOpenPID 获取插件用户openPID
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/basic-info/getPluginOpenPId.html
|
||||||
|
func (auth *Auth) GetPluginOpenPID(code string) (string, error) {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = auth.GetAccessToken(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
req := &GetPluginOpenPIDRequest{
|
||||||
|
Code: code,
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(fmt.Sprintf(getPluginOpenPIDURL, accessToken), req); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
result := &GetPluginOpenPIDResponse{}
|
||||||
|
err = util.DecodeWithError(response, result, "GetPluginOpenPID")
|
||||||
|
return result.OpenPID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserEncryptKeyResponse 获取用户encryptKey响应
|
||||||
|
type GetUserEncryptKeyResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
KeyInfoList []KeyInfo `json:"key_info_list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyInfo 用户最近三次的加密key
|
||||||
|
type KeyInfo struct {
|
||||||
|
EncryptKey string `json:"encrypt_key"`
|
||||||
|
Version int64 `json:"version"`
|
||||||
|
ExpireIn int64 `json:"expire_in"`
|
||||||
|
Iv string `json:"iv"`
|
||||||
|
CreateTime int64 `json:"create_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserEncryptKey 获取用户encryptKey
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/internet/getUserEncryptKey.html
|
||||||
|
func (auth *Auth) GetUserEncryptKey(signature, openID string) (*GetUserEncryptKeyResponse, error) {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = auth.GetAccessToken(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.HTTPGet(fmt.Sprintf(getUserEncryptKeyURL, accessToken, signature, openID)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &GetUserEncryptKeyResponse{}
|
||||||
|
err = util.DecodeWithError(response, result, "GetUserEncryptKey")
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|||||||
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// createActivityURL 创建activity_id
|
// createActivityIDURL 创建activity_id
|
||||||
createActivityURL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/activityid/create?access_token=%s"
|
createActivityIDURL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/activityid/create?access_token=%s&unionid=%s&openid=%s"
|
||||||
// SendUpdatableMsgURL 修改动态消息
|
// SendUpdatableMsgURL 修改动态消息
|
||||||
setUpdatableMsgURL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/updatablemsg/send?access_token=%s"
|
setUpdatableMsgURL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/updatablemsg/send?access_token=%s"
|
||||||
|
// setChatToolMsgURL 修改小程序聊天工具的动态卡片消息
|
||||||
|
setChatToolMsgURL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/chattoolmsg/send?access_token=%s"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UpdatableTargetState 动态消息状态
|
// UpdatableTargetState 动态消息状态
|
||||||
@@ -38,15 +40,26 @@ func NewUpdatableMessage(ctx *context.Context) *UpdatableMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateActivityIDRequest 创建activity_id请求
|
||||||
|
type CreateActivityIDRequest struct {
|
||||||
|
UnionID string
|
||||||
|
OpenID string
|
||||||
|
}
|
||||||
|
|
||||||
// CreateActivityID 创建activity_id
|
// CreateActivityID 创建activity_id
|
||||||
func (updatableMessage *UpdatableMessage) CreateActivityID() (res CreateActivityIDResponse, err error) {
|
func (updatableMessage *UpdatableMessage) CreateActivityID() (CreateActivityIDResponse, error) {
|
||||||
|
return updatableMessage.CreateActivityIDWithReq(&CreateActivityIDRequest{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateActivityIDWithReq 创建activity_id
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/updatable-message/createActivityId.html
|
||||||
|
func (updatableMessage *UpdatableMessage) CreateActivityIDWithReq(req *CreateActivityIDRequest) (res CreateActivityIDResponse, err error) {
|
||||||
accessToken, err := updatableMessage.GetAccessToken()
|
accessToken, err := updatableMessage.GetAccessToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
url := fmt.Sprintf(createActivityIDURL, accessToken, req.UnionID, req.OpenID)
|
||||||
uri := fmt.Sprintf(createActivityURL, accessToken)
|
response, err := util.HTTPGet(url)
|
||||||
response, err := util.HTTPGet(uri)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -100,3 +113,35 @@ type SendUpdatableMsgReq struct {
|
|||||||
TemplateInfo UpdatableMsgTemplate `json:"template_info"`
|
TemplateInfo UpdatableMsgTemplate `json:"template_info"`
|
||||||
TargetState UpdatableTargetState `json:"target_state"`
|
TargetState UpdatableTargetState `json:"target_state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetChatToolMsgRequest 修改小程序聊天工具的动态卡片消息请求
|
||||||
|
type SetChatToolMsgRequest struct {
|
||||||
|
VersionType int64 `json:"version_type"`
|
||||||
|
TargetState UpdatableTargetState `json:"target_state"`
|
||||||
|
ActivityID string `json:"activity_id"`
|
||||||
|
TemplateID string `json:"template_id"`
|
||||||
|
ParticipatorInfoList []ParticipatorInfo `json:"participator_info_list,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParticipatorInfo 更新后的聊天室成员状态
|
||||||
|
type ParticipatorInfo struct {
|
||||||
|
State int `json:"state"`
|
||||||
|
GroupOpenID string `json:"group_openid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetChatToolMsg 修改小程序聊天工具的动态卡片消息
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/updatable-message/setChatToolMsg.html
|
||||||
|
func (updatableMessage *UpdatableMessage) SetChatToolMsg(req *SetChatToolMsgRequest) error {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = updatableMessage.GetAccessToken(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(fmt.Sprintf(setChatToolMsgURL, accessToken), req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return util.DecodeWithCommonError(response, "SetChatToolMsg")
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package security
|
package security
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
context2 "context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@@ -64,7 +65,12 @@ type MediaCheckAsyncRequest struct {
|
|||||||
|
|
||||||
// MediaCheckAsync 异步校验图片/音频是否含有违法违规内容
|
// MediaCheckAsync 异步校验图片/音频是否含有违法违规内容
|
||||||
func (security *Security) MediaCheckAsync(in *MediaCheckAsyncRequest) (traceID string, err error) {
|
func (security *Security) MediaCheckAsync(in *MediaCheckAsyncRequest) (traceID string, err error) {
|
||||||
accessToken, err := security.GetAccessToken()
|
return security.MediaCheckAsyncContext(context2.Background(), in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MediaCheckAsyncContext 异步校验图片/音频是否含有违法违规内容
|
||||||
|
func (security *Security) MediaCheckAsyncContext(ctx context2.Context, in *MediaCheckAsyncRequest) (traceID string, err error) {
|
||||||
|
accessToken, err := security.GetAccessTokenContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -77,7 +83,7 @@ func (security *Security) MediaCheckAsync(in *MediaCheckAsyncRequest) (traceID s
|
|||||||
req.Version = 2
|
req.Version = 2
|
||||||
|
|
||||||
uri := fmt.Sprintf(mediaCheckAsyncURL, accessToken)
|
uri := fmt.Sprintf(mediaCheckAsyncURL, accessToken)
|
||||||
response, err := util.PostJSON(uri, req)
|
response, err := util.PostJSONContext(ctx, uri, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -222,7 +228,12 @@ func (security *Security) MsgCheckV1(content string) (res MsgCheckResponse, err
|
|||||||
|
|
||||||
// MsgCheck 检查一段文本是否含有违法违规内容
|
// MsgCheck 检查一段文本是否含有违法违规内容
|
||||||
func (security *Security) MsgCheck(in *MsgCheckRequest) (res MsgCheckResponse, err error) {
|
func (security *Security) MsgCheck(in *MsgCheckRequest) (res MsgCheckResponse, err error) {
|
||||||
accessToken, err := security.GetAccessToken()
|
return security.MsgCheckContext(context2.Background(), in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgCheckContext 检查一段文本是否含有违法违规内容
|
||||||
|
func (security *Security) MsgCheckContext(ctx context2.Context, in *MsgCheckRequest) (res MsgCheckResponse, err error) {
|
||||||
|
accessToken, err := security.GetAccessTokenContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -235,7 +246,7 @@ func (security *Security) MsgCheck(in *MsgCheckRequest) (res MsgCheckResponse, e
|
|||||||
req.Version = 2
|
req.Version = 2
|
||||||
|
|
||||||
uri := fmt.Sprintf(msgCheckURL, accessToken)
|
uri := fmt.Sprintf(msgCheckURL, accessToken)
|
||||||
response, err := util.PostJSON(uri, req)
|
response, err := util.PostJSONContext(ctx, uri, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,22 +12,30 @@ const (
|
|||||||
// 发送订阅消息
|
// 发送订阅消息
|
||||||
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.send.html
|
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.send.html
|
||||||
subscribeSendURL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send"
|
subscribeSendURL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send"
|
||||||
|
|
||||||
// 获取当前帐号下的个人模板列表
|
// 获取当前帐号下的个人模板列表
|
||||||
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.getTemplateList.html
|
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.getTemplateList.html
|
||||||
getTemplateURL = "https://api.weixin.qq.com/wxaapi/newtmpl/gettemplate"
|
getTemplateURL = "https://api.weixin.qq.com/wxaapi/newtmpl/gettemplate"
|
||||||
|
|
||||||
// 添加订阅模板
|
// 添加订阅模板
|
||||||
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.addTemplate.html
|
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.addTemplate.html
|
||||||
addTemplateURL = "https://api.weixin.qq.com/wxaapi/newtmpl/addtemplate"
|
addTemplateURL = "https://api.weixin.qq.com/wxaapi/newtmpl/addtemplate"
|
||||||
|
|
||||||
// 删除私有模板
|
// 删除私有模板
|
||||||
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.deleteTemplate.html
|
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.deleteTemplate.html
|
||||||
delTemplateURL = "https://api.weixin.qq.com/wxaapi/newtmpl/deltemplate"
|
delTemplateURL = "https://api.weixin.qq.com/wxaapi/newtmpl/deltemplate"
|
||||||
|
|
||||||
// 统一服务消息
|
// 统一服务消息
|
||||||
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/uniform-message/uniformMessage.send.html
|
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/uniform-message/uniformMessage.send.html
|
||||||
uniformMessageSend = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send"
|
uniformMessageSend = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send"
|
||||||
|
// getCategoryURL 获取类目
|
||||||
|
getCategoryURL = "https://api.weixin.qq.com/wxaapi/newtmpl/getcategory?access_token=%s"
|
||||||
|
// getPubTemplateKeyWordsByIDURL 获取关键词列表
|
||||||
|
getPubTemplateKeyWordsByIDURL = "https://api.weixin.qq.com/wxaapi/newtmpl/getpubtemplatekeywords?access_token=%s&tid=%s"
|
||||||
|
// getPubTemplateTitleListURL 获取所属类目下的公共模板
|
||||||
|
getPubTemplateTitleListURL = "https://api.weixin.qq.com/wxaapi/newtmpl/getpubtemplatetitles?access_token=%s&ids=%s&start=%d&limit=%d"
|
||||||
|
// setUserNotifyURL 激活与更新服务卡片
|
||||||
|
setUserNotifyURL = "https://api.weixin.qq.com/wxa/set_user_notify?access_token=%s"
|
||||||
|
// setUserNotifyExtURL 更新服务卡片扩展信息
|
||||||
|
setUserNotifyExtURL = "https://api.weixin.qq.com/wxa/set_user_notifyext?access_token=%s"
|
||||||
|
// getUserNotifyURL 查询服务卡片状态
|
||||||
|
getUserNotifyURL = "https://api.weixin.qq.com/wxa/get_user_notify?access_token=%s"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Subscribe 订阅消息
|
// Subscribe 订阅消息
|
||||||
@@ -58,11 +66,18 @@ type DataItem struct {
|
|||||||
|
|
||||||
// TemplateItem template item
|
// TemplateItem template item
|
||||||
type TemplateItem struct {
|
type TemplateItem struct {
|
||||||
PriTmplID string `json:"priTmplId"`
|
PriTmplID string `json:"priTmplId"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
Example string `json:"example"`
|
Example string `json:"example"`
|
||||||
Type int64 `json:"type"`
|
Type int64 `json:"type"`
|
||||||
|
KeywordEnumValueList []KeywordEnumValue `json:"keywordEnumValueList"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeywordEnumValue 枚举参数值范围
|
||||||
|
type KeywordEnumValue struct {
|
||||||
|
EnumValueList []string `json:"enumValueList"`
|
||||||
|
KeywordCode string `json:"keywordCode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TemplateList template list
|
// TemplateList template list
|
||||||
@@ -224,3 +239,200 @@ func (s *Subscribe) Delete(templateID string) (err error) {
|
|||||||
}
|
}
|
||||||
return util.DecodeWithCommonError(response, "DeleteSubscribe")
|
return util.DecodeWithCommonError(response, "DeleteSubscribe")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCategoryResponse 获取类目响应
|
||||||
|
type GetCategoryResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
Data []Category `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Category 类目
|
||||||
|
type Category struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCategory 获取类目
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/subscribe-message/getCategory.html
|
||||||
|
func (s *Subscribe) GetCategory() ([]Category, error) {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = s.GetAccessToken(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.HTTPGet(fmt.Sprintf(getCategoryURL, accessToken)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &GetCategoryResponse{}
|
||||||
|
err = util.DecodeWithError(response, result, "GetCategory")
|
||||||
|
return result.Data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPubTemplateKeywordsByIDResponse 获取关键词列表响应
|
||||||
|
type GetPubTemplateKeywordsByIDResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
Count int64 `json:"count"`
|
||||||
|
Data []PubTemplateKeywords `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PubTemplateKeywords 关键词
|
||||||
|
type PubTemplateKeywords struct {
|
||||||
|
KID int64 `json:"kid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Example string `json:"example"`
|
||||||
|
Rule string `json:"rule"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPubTemplateKeywordsByID 获取关键词列表
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/subscribe-message/getPubTemplateKeyWordsById.html
|
||||||
|
func (s *Subscribe) GetPubTemplateKeywordsByID(tid string) (*GetPubTemplateKeywordsByIDResponse, error) {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = s.GetAccessToken(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.HTTPGet(fmt.Sprintf(getPubTemplateKeyWordsByIDURL, accessToken, tid)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &GetPubTemplateKeywordsByIDResponse{}
|
||||||
|
err = util.DecodeWithError(response, result, "GetPubTemplateKeywordsByID")
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPubTemplateTitleListRequest 获取所属类目下的公共模板请求
|
||||||
|
type GetPubTemplateTitleListRequest struct {
|
||||||
|
Start int64
|
||||||
|
Limit int64
|
||||||
|
IDs string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPubTemplateTitleListResponse 获取所属类目下的公共模板响应
|
||||||
|
type GetPubTemplateTitleListResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
Count int64 `json:"count"`
|
||||||
|
Data []PubTemplateTitle `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PubTemplateTitle 模板标题
|
||||||
|
type PubTemplateTitle struct {
|
||||||
|
Type int64 `json:"type"`
|
||||||
|
TID string `json:"tid"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
CategoryID string `json:"categoryId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPubTemplateTitleList 获取所属类目下的公共模板
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/subscribe-message/getPubTemplateTitleList.html
|
||||||
|
func (s *Subscribe) GetPubTemplateTitleList(req *GetPubTemplateTitleListRequest) (*GetPubTemplateTitleListResponse, error) {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = s.GetAccessToken(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.HTTPGet(fmt.Sprintf(getPubTemplateTitleListURL, accessToken, req.IDs, req.Start, req.Limit)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &GetPubTemplateTitleListResponse{}
|
||||||
|
err = util.DecodeWithError(response, result, "GetPubTemplateTitleList")
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserNotifyRequest 激活与更新服务卡片请求
|
||||||
|
type SetUserNotifyRequest struct {
|
||||||
|
OpenID string `json:"openid"`
|
||||||
|
NotifyType int64 `json:"notify_type"`
|
||||||
|
NotifyCode string `json:"notify_code"`
|
||||||
|
ContentJSON string `json:"content_json"`
|
||||||
|
CheckJSON string `json:"check_json,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserNotify 激活与更新服务卡片
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/subscribe-message/setUserNotify.html
|
||||||
|
func (s *Subscribe) SetUserNotify(req *SetUserNotifyRequest) error {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = s.GetAccessToken(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(fmt.Sprintf(setUserNotifyURL, accessToken), req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return util.DecodeWithCommonError(response, "SetUserNotify")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserNotifyExtRequest 更新服务卡片扩展信息请求
|
||||||
|
type SetUserNotifyExtRequest struct {
|
||||||
|
OpenID string `json:"openid"`
|
||||||
|
NotifyType int64 `json:"notify_type"`
|
||||||
|
NotifyCode string `json:"notify_code"`
|
||||||
|
ExtJSON string `json:"ext_json"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserNotifyExt 更新服务卡片扩展信息
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/subscribe-message/setUserNotifyExt.html
|
||||||
|
func (s *Subscribe) SetUserNotifyExt(req *SetUserNotifyExtRequest) error {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = s.GetAccessToken(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(fmt.Sprintf(setUserNotifyExtURL, accessToken), req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return util.DecodeWithCommonError(response, "SetUserNotifyExt")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserNotifyRequest 查询服务卡片状态请求
|
||||||
|
type GetUserNotifyRequest struct {
|
||||||
|
OpenID string `json:"openid"`
|
||||||
|
NotifyType int64 `json:"notify_type"`
|
||||||
|
NotifyCode string `json:"notify_code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserNotifyResponse 查询服务卡片状态响应
|
||||||
|
type GetUserNotifyResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
NotifyInfo NotifyInfo `json:"notify_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyInfo 卡片状态
|
||||||
|
type NotifyInfo struct {
|
||||||
|
NotifyType int64 `json:"notify_type"`
|
||||||
|
ContentJSON string `json:"content_json"`
|
||||||
|
CodeState int64 `json:"code_state"`
|
||||||
|
CodeExpireTime int64 `json:"code_expire_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserNotify 查询服务卡片状态
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/subscribe-message/getUserNotify.html
|
||||||
|
func (s *Subscribe) GetUserNotify(req *GetUserNotifyRequest) (*GetUserNotifyResponse, error) {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = s.GetAccessToken(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(fmt.Sprintf(getUserNotifyURL, accessToken), req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &GetUserNotifyResponse{}
|
||||||
|
err = util.DecodeWithError(response, result, "GetUserNotify")
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,13 @@ import (
|
|||||||
"github.com/silenceper/wechat/v2/util"
|
"github.com/silenceper/wechat/v2/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const queryURL = "https://api.weixin.qq.com/wxa/query_urllink"
|
const queryURL = "https://api.weixin.qq.com/wxa/query_urllink?access_token=%s"
|
||||||
|
|
||||||
|
// ULQueryRequest 查询加密URLLink请求
|
||||||
|
type ULQueryRequest struct {
|
||||||
|
URLLink string `json:"url_link"`
|
||||||
|
QueryType int `json:"query_type"`
|
||||||
|
}
|
||||||
|
|
||||||
// ULQueryResult 返回的结果
|
// ULQueryResult 返回的结果
|
||||||
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-link/urllink.query.html 返回值
|
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-link/urllink.query.html 返回值
|
||||||
@@ -28,25 +34,35 @@ type ULQueryResult struct {
|
|||||||
ResourceAppid string `json:"resource_appid"`
|
ResourceAppid string `json:"resource_appid"`
|
||||||
} `json:"cloud_base"`
|
} `json:"cloud_base"`
|
||||||
} `json:"url_link_info"`
|
} `json:"url_link_info"`
|
||||||
VisitOpenid string `json:"visit_openid"`
|
VisitOpenid string `json:"visit_openid"`
|
||||||
|
QuotaInfo QuotaInfo `json:"quota_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuotaInfo quota 配置
|
||||||
|
type QuotaInfo struct {
|
||||||
|
RemainVisitQuota int64 `json:"remain_visit_quota"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query 查询小程序 url_link 配置。
|
// Query 查询小程序 url_link 配置。
|
||||||
func (u *URLLink) Query(urlLink string) (*ULQueryResult, error) {
|
func (u *URLLink) Query(urlLink string) (*ULQueryResult, error) {
|
||||||
accessToken, err := u.GetAccessToken()
|
return u.QueryWithType(&ULQueryRequest{URLLink: urlLink})
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
}
|
// QueryWithType 查询加密URLLink
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/url-link/queryUrlLink.html
|
||||||
uri := fmt.Sprintf("%s?access_token=%s", queryURL, accessToken)
|
func (u *URLLink) QueryWithType(req *ULQueryRequest) (*ULQueryResult, error) {
|
||||||
response, err := util.PostJSON(uri, map[string]string{"url_link": urlLink})
|
var (
|
||||||
if err != nil {
|
accessToken string
|
||||||
return nil, err
|
err error
|
||||||
}
|
)
|
||||||
var resp ULQueryResult
|
if accessToken, err = u.GetAccessToken(); err != nil {
|
||||||
err = util.DecodeWithError(response, &resp, "URLLink.Query")
|
return nil, err
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
var response []byte
|
||||||
}
|
if response, err = util.PostJSON(fmt.Sprintf(queryURL, accessToken), req); err != nil {
|
||||||
return &resp, nil
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &ULQueryResult{}
|
||||||
|
err = util.DecodeWithError(response, result, "URLLink.Query")
|
||||||
|
return result, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ const (
|
|||||||
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-scheme/urlscheme.query.html#参数
|
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-scheme/urlscheme.query.html#参数
|
||||||
type QueryScheme struct {
|
type QueryScheme struct {
|
||||||
// 小程序 scheme 码
|
// 小程序 scheme 码
|
||||||
Scheme string `json:"scheme"`
|
Scheme string `json:"scheme"`
|
||||||
|
QueryType int `json:"query_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SchemeInfo scheme 配置
|
// SchemeInfo scheme 配置
|
||||||
@@ -33,34 +34,47 @@ type SchemeInfo struct {
|
|||||||
EnvVersion EnvVersion `json:"env_version"`
|
EnvVersion EnvVersion `json:"env_version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// resQueryScheme 返回结构体
|
// QuotaInfo quota 配置
|
||||||
|
type QuotaInfo struct {
|
||||||
|
RemainVisitQuota int64 `json:"remain_visit_quota"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResQueryScheme 返回结构体
|
||||||
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-scheme/urlscheme.query.html#参数
|
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-scheme/urlscheme.query.html#参数
|
||||||
type resQueryScheme struct {
|
type ResQueryScheme struct {
|
||||||
// 通用错误
|
// 通用错误
|
||||||
util.CommonError
|
util.CommonError
|
||||||
// scheme 配置
|
// scheme 配置
|
||||||
SchemeInfo SchemeInfo `json:"scheme_info"`
|
SchemeInfo SchemeInfo `json:"scheme_info"`
|
||||||
// 访问该链接的openid,没有用户访问过则为空字符串
|
// 访问该链接的openid,没有用户访问过则为空字符串
|
||||||
VisitOpenid string `json:"visit_openid"`
|
VisitOpenid string `json:"visit_openid"`
|
||||||
|
QuotaInfo QuotaInfo `json:"quota_info"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryScheme 查询小程序 scheme 码
|
// QueryScheme 查询小程序 scheme 码
|
||||||
func (u *URLScheme) QueryScheme(querySchemeParams QueryScheme) (schemeInfo SchemeInfo, visitOpenid string, err error) {
|
func (u *URLScheme) QueryScheme(querySchemeParams QueryScheme) (schemeInfo SchemeInfo, visitOpenid string, err error) {
|
||||||
var accessToken string
|
res, err := u.QuerySchemeWithRes(querySchemeParams)
|
||||||
accessToken, err = u.GetAccessToken()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
urlStr := fmt.Sprintf(querySchemeURL, accessToken)
|
|
||||||
var response []byte
|
|
||||||
response, err = util.PostJSON(urlStr, querySchemeParams)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用通用方法返回错误
|
|
||||||
var res resQueryScheme
|
|
||||||
err = util.DecodeWithError(response, &res, "QueryScheme")
|
|
||||||
return res.SchemeInfo, res.VisitOpenid, err
|
return res.SchemeInfo, res.VisitOpenid, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QuerySchemeWithRes 查询scheme码
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/url-scheme/queryScheme.html
|
||||||
|
func (u *URLScheme) QuerySchemeWithRes(req QueryScheme) (*ResQueryScheme, error) {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = u.GetAccessToken(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(fmt.Sprintf(querySchemeURL, accessToken), req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &ResQueryScheme{}
|
||||||
|
err = util.DecodeWithError(response, result, "QueryScheme")
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,7 +17,12 @@ func NewURLScheme(ctx *context.Context) *URLScheme {
|
|||||||
return &URLScheme{Context: ctx}
|
return &URLScheme{Context: ctx}
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateURL = "https://api.weixin.qq.com/wxa/generatescheme"
|
const (
|
||||||
|
// generateURL 获取加密scheme码
|
||||||
|
generateURL = "https://api.weixin.qq.com/wxa/generatescheme"
|
||||||
|
// generateNFCURL 获取 NFC 的小程序 scheme
|
||||||
|
generateNFCURL = "https://api.weixin.qq.com/wxa/generatenfcscheme?access_token=%s"
|
||||||
|
)
|
||||||
|
|
||||||
// TExpireType 失效类型 (指定时间戳/指定间隔)
|
// TExpireType 失效类型 (指定时间戳/指定间隔)
|
||||||
type TExpireType int
|
type TExpireType int
|
||||||
@@ -50,11 +55,13 @@ type JumpWxa struct {
|
|||||||
// USParams 请求参数
|
// USParams 请求参数
|
||||||
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-scheme/urlscheme.generate.html#请求参数
|
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-scheme/urlscheme.generate.html#请求参数
|
||||||
type USParams struct {
|
type USParams struct {
|
||||||
JumpWxa *JumpWxa `json:"jump_wxa"`
|
JumpWxa *JumpWxa `json:"jump_wxa,omitempty"`
|
||||||
ExpireType TExpireType `json:"expire_type"`
|
ExpireType TExpireType `json:"expire_type,omitempty"`
|
||||||
ExpireTime int64 `json:"expire_time"`
|
ExpireTime int64 `json:"expire_time,omitempty"`
|
||||||
ExpireInterval int `json:"expire_interval"`
|
ExpireInterval int `json:"expire_interval,omitempty"`
|
||||||
IsExpire bool `json:"is_expire,omitempty"`
|
IsExpire bool `json:"is_expire,omitempty"`
|
||||||
|
ModelID string `json:"model_id,omitempty"`
|
||||||
|
Sn string `json:"sn,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// USResult 返回的结果
|
// USResult 返回的结果
|
||||||
@@ -81,3 +88,22 @@ func (u *URLScheme) Generate(params *USParams) (string, error) {
|
|||||||
err = util.DecodeWithError(response, &resp, "URLScheme.Generate")
|
err = util.DecodeWithError(response, &resp, "URLScheme.Generate")
|
||||||
return resp.OpenLink, err
|
return resp.OpenLink, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenerateNFC 获取 NFC 的小程序 scheme
|
||||||
|
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/url-scheme/generateNFCScheme.html
|
||||||
|
func (u *URLScheme) GenerateNFC(params *USParams) (string, error) {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = u.GetAccessToken(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(fmt.Sprintf(generateNFCURL, accessToken), params); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
result := &USResult{}
|
||||||
|
err = util.DecodeWithError(response, result, "URLScheme.GenerateNFC")
|
||||||
|
return result.OpenLink, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package js
|
package js
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
context2 "context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/silenceper/wechat/v2/credential"
|
"github.com/silenceper/wechat/v2/credential"
|
||||||
@@ -39,20 +40,40 @@ func (js *Js) SetJsTicketHandle(ticketHandle credential.JsTicketHandle) {
|
|||||||
// GetConfig 获取jssdk需要的配置参数
|
// GetConfig 获取jssdk需要的配置参数
|
||||||
// uri 为当前网页地址
|
// uri 为当前网页地址
|
||||||
func (js *Js) GetConfig(uri string) (config *Config, err error) {
|
func (js *Js) GetConfig(uri string) (config *Config, err error) {
|
||||||
|
return js.GetConfigContext(context2.Background(), uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfigContext 新方法,允许传入上下文,避免协程泄漏
|
||||||
|
func (js *Js) GetConfigContext(ctx context2.Context, uri string) (config *Config, err error) {
|
||||||
var accessToken string
|
var accessToken string
|
||||||
accessToken, err = js.GetAccessToken()
|
// 类型断言,如果断言成功,调用安全的 GetAccessTokenContext 方法
|
||||||
|
if ctxHandle, ok := js.Context.AccessTokenHandle.(credential.AccessTokenContextHandle); ok {
|
||||||
|
accessToken, err = ctxHandle.GetAccessTokenContext(ctx)
|
||||||
|
} else {
|
||||||
|
// 如果没有实现 AccessTokenContextHandle 接口,调用旧的 GetAccessToken 方法
|
||||||
|
accessToken, err = js.Context.GetAccessToken()
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var ticketStr string
|
var ticketStr string
|
||||||
ticketStr, err = js.GetTicket(accessToken)
|
// 类型断言 jsTicket
|
||||||
|
if ticketCtxHandle, ok := js.JsTicketHandle.(credential.JsTicketContextHandle); ok {
|
||||||
|
ticketStr, err = ticketCtxHandle.GetTicketContext(ctx, accessToken)
|
||||||
|
} else {
|
||||||
|
// 如果没有实现 JsTicketContextHandle 接口,调用旧的 GetTicket 方法
|
||||||
|
ticketStr, err = js.GetTicket(accessToken)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
nonceStr := util.RandomStr(16)
|
nonceStr := util.RandomStr(16)
|
||||||
timestamp := util.GetCurrTS()
|
timestamp := util.GetCurrTS()
|
||||||
str := fmt.Sprintf("jsapi_ticket=%s&noncestr=%s×tamp=%d&url=%s", ticketStr, nonceStr, timestamp, uri)
|
str := fmt.Sprintf("jsapi_ticket=%s&noncestr=%s×tamp=%d&url=%s", ticketStr, nonceStr, timestamp, uri)
|
||||||
sigStr := util.Signature(str)
|
sigStr := util.Signature(str)
|
||||||
|
|
||||||
config = new(Config)
|
config = new(Config)
|
||||||
config.AppID = js.AppID
|
config.AppID = js.AppID
|
||||||
config.NonceStr = nonceStr
|
config.NonceStr = nonceStr
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package account
|
|||||||
|
|
||||||
import "github.com/silenceper/wechat/v2/openplatform/context"
|
import "github.com/silenceper/wechat/v2/openplatform/context"
|
||||||
|
|
||||||
// Account 开放平台张哈管理
|
// Account 开放平台帐号管理
|
||||||
// TODO 实现方法
|
// TODO 实现方法
|
||||||
type Account struct {
|
type Account struct {
|
||||||
*context.Context
|
*context.Context
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const (
|
|||||||
getComponentInfoURL = "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_info?component_access_token=%s"
|
getComponentInfoURL = "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_info?component_access_token=%s"
|
||||||
componentLoginURL = "https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=%s&pre_auth_code=%s&redirect_uri=%s&auth_type=%d&biz_appid=%s"
|
componentLoginURL = "https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=%s&pre_auth_code=%s&redirect_uri=%s&auth_type=%d&biz_appid=%s"
|
||||||
bindComponentURL = "https://mp.weixin.qq.com/safe/bindcomponent?action=bindcomponent&auth_type=%d&no_scan=1&component_appid=%s&pre_auth_code=%s&redirect_uri=%s&biz_appid=%s#wechat_redirect"
|
bindComponentURL = "https://mp.weixin.qq.com/safe/bindcomponent?action=bindcomponent&auth_type=%d&no_scan=1&component_appid=%s&pre_auth_code=%s&redirect_uri=%s&biz_appid=%s#wechat_redirect"
|
||||||
|
bindComponentURLV2 = "https://open.weixin.qq.com/wxaopen/safe/bindcomponent?action=bindcomponent&auth_type=%d&no_scan=1&component_appid=%s&pre_auth_code=%s&redirect_uri=%s&biz_appid=%s#wechat_redirect"
|
||||||
// TODO 获取授权方选项信息
|
// TODO 获取授权方选项信息
|
||||||
// getComponentConfigURL = "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_option?component_access_token=%s"
|
// getComponentConfigURL = "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_option?component_access_token=%s"
|
||||||
// TODO 获取已授权的账号信息
|
// TODO 获取已授权的账号信息
|
||||||
@@ -137,6 +138,20 @@ func (ctx *Context) GetBindComponentURL(redirectURI string, authType int, bizApp
|
|||||||
return ctx.GetBindComponentURLContext(context.Background(), redirectURI, authType, bizAppID)
|
return ctx.GetBindComponentURLContext(context.Background(), redirectURI, authType, bizAppID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBindComponentURLV2Context 获取新版本第三方公众号授权链接(链接跳转,适用移动端)
|
||||||
|
func (ctx *Context) GetBindComponentURLV2Context(stdCtx context.Context, redirectURI string, authType int, bizAppID string) (string, error) {
|
||||||
|
code, err := ctx.GetPreCodeContext(stdCtx)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(bindComponentURLV2, authType, ctx.AppID, code, url.QueryEscape(redirectURI), bizAppID), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBindComponentURLV2 获取新版本第三方公众号授权链接(链接跳转,适用移动端)
|
||||||
|
func (ctx *Context) GetBindComponentURLV2(redirectURI string, authType int, bizAppID string) (string, error) {
|
||||||
|
return ctx.GetBindComponentURLContext(context.Background(), redirectURI, authType, bizAppID)
|
||||||
|
}
|
||||||
|
|
||||||
// ID 微信返回接口中各种类型字段
|
// ID 微信返回接口中各种类型字段
|
||||||
type ID struct {
|
type ID struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
@@ -225,6 +240,10 @@ func (ctx *Context) RefreshAuthrTokenContext(stdCtx context.Context, appid, refr
|
|||||||
if err := cache.SetContext(stdCtx, ctx.Cache, authrTokenKey, ret.AccessToken, time.Second*time.Duration(ret.ExpiresIn-30)); err != nil {
|
if err := cache.SetContext(stdCtx, ctx.Cache, authrTokenKey, ret.AccessToken, time.Second*time.Duration(ret.ExpiresIn-30)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
refreshTokenKey := "authorizer_refresh_token_" + appid
|
||||||
|
if err := cache.SetContext(stdCtx, ctx.Cache, refreshTokenKey, ret.RefreshToken, 10*365*24*60*60*time.Second); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,8 +257,18 @@ func (ctx *Context) GetAuthrAccessTokenContext(stdCtx context.Context, appid str
|
|||||||
authrTokenKey := "authorizer_access_token_" + appid
|
authrTokenKey := "authorizer_access_token_" + appid
|
||||||
val := cache.GetContext(stdCtx, ctx.Cache, authrTokenKey)
|
val := cache.GetContext(stdCtx, ctx.Cache, authrTokenKey)
|
||||||
if val == nil {
|
if val == nil {
|
||||||
return "", fmt.Errorf("cannot get authorizer %s access token", appid)
|
refreshTokenKey := "authorizer_refresh_token_" + appid
|
||||||
|
val := cache.GetContext(stdCtx, ctx.Cache, refreshTokenKey)
|
||||||
|
if val == nil {
|
||||||
|
return "", fmt.Errorf("cannot get authorizer %s refresh token", appid)
|
||||||
|
}
|
||||||
|
token, err := ctx.RefreshAuthrTokenContext(stdCtx, appid, val.(string))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return token.AccessToken, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return val.(string), nil
|
return val.(string), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package js
|
package js
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
context2 "context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/silenceper/wechat/v2/credential"
|
"github.com/silenceper/wechat/v2/credential"
|
||||||
@@ -32,14 +33,31 @@ func (js *Js) SetJsTicketHandle(ticketHandle credential.JsTicketHandle) {
|
|||||||
// GetConfig 第三方平台 - 获取jssdk需要的配置参数
|
// GetConfig 第三方平台 - 获取jssdk需要的配置参数
|
||||||
// uri 为当前网页地址
|
// uri 为当前网页地址
|
||||||
func (js *Js) GetConfig(uri, appid string) (config *officialJs.Config, err error) {
|
func (js *Js) GetConfig(uri, appid string) (config *officialJs.Config, err error) {
|
||||||
config = new(officialJs.Config)
|
return js.GetConfigContext(context2.Background(), uri, appid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfigContext 新方法,允许传入上下文,避免协程泄漏
|
||||||
|
func (js *Js) GetConfigContext(ctx context2.Context, uri, appid string) (config *officialJs.Config, err error) {
|
||||||
var accessToken string
|
var accessToken string
|
||||||
accessToken, err = js.GetAccessToken()
|
// 类型断言,如果断言成功,调用安全的 GetAccessTokenContext 方法
|
||||||
|
if ctxHandle, ok := js.Context.AccessTokenHandle.(credential.AccessTokenContextHandle); ok {
|
||||||
|
accessToken, err = ctxHandle.GetAccessTokenContext(ctx)
|
||||||
|
} else {
|
||||||
|
// 如果没有实现 AccessTokenContextHandle 接口,调用旧的 GetAccessToken 方法
|
||||||
|
accessToken, err = js.Context.GetAccessToken()
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var ticketStr string
|
var ticketStr string
|
||||||
ticketStr, err = js.GetTicket(accessToken)
|
// 类型断言 jsTicket
|
||||||
|
if ticketCtxHandle, ok := js.JsTicketHandle.(credential.JsTicketContextHandle); ok {
|
||||||
|
ticketStr, err = ticketCtxHandle.GetTicketContext(ctx, accessToken)
|
||||||
|
} else {
|
||||||
|
// 如果没有实现 JsTicketContextHandle 接口,调用旧的 GetTicket 方法
|
||||||
|
ticketStr, err = js.GetTicket(accessToken)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -49,6 +67,7 @@ func (js *Js) GetConfig(uri, appid string) (config *officialJs.Config, err error
|
|||||||
str := fmt.Sprintf("jsapi_ticket=%s&noncestr=%s×tamp=%d&url=%s", ticketStr, nonceStr, timestamp, uri)
|
str := fmt.Sprintf("jsapi_ticket=%s&noncestr=%s×tamp=%d&url=%s", ticketStr, nonceStr, timestamp, uri)
|
||||||
sigStr := util.Signature(str)
|
sigStr := util.Signature(str)
|
||||||
|
|
||||||
|
config = new(officialJs.Config)
|
||||||
config.AppID = appid
|
config.AppID = appid
|
||||||
config.NonceStr = nonceStr
|
config.NonceStr = nonceStr
|
||||||
config.Timestamp = timestamp
|
config.Timestamp = timestamp
|
||||||
|
|||||||
147
openplatform/officialaccount/js/js_test.go
Normal file
147
openplatform/officialaccount/js/js_test.go
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
// 验证 js.GetConfigContext 是否能正确传递上下文到 HTTP 请求,确保上下文正确传播,防止在获取 JSSDK 配置时发生协程泄露。
|
||||||
|
package js
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
context2 "context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/silenceper/wechat/v2/cache"
|
||||||
|
"github.com/silenceper/wechat/v2/credential"
|
||||||
|
"github.com/silenceper/wechat/v2/officialaccount/config"
|
||||||
|
"github.com/silenceper/wechat/v2/officialaccount/context"
|
||||||
|
"github.com/silenceper/wechat/v2/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mockAccessTokenHandle 模拟 AccessTokenHandle
|
||||||
|
type mockAccessTokenHandle struct{}
|
||||||
|
|
||||||
|
func (m *mockAccessTokenHandle) GetAccessToken() (string, error) {
|
||||||
|
return "mock-access-token", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockAccessTokenHandle) GetAccessTokenContext(_ context2.Context) (string, error) {
|
||||||
|
return "mock-access-token", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// contextCheckingRoundTripper 自定义 RoundTripper 用于检查 context
|
||||||
|
type contextCheckingRoundTripper struct {
|
||||||
|
originalCtx context2.Context
|
||||||
|
t *testing.T
|
||||||
|
key interface{}
|
||||||
|
expectedVal interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *contextCheckingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
// 获取请求中的 context
|
||||||
|
reqCtx := req.Context()
|
||||||
|
|
||||||
|
// 打印 context 比较结果
|
||||||
|
rt.t.Logf("比较上下文的内存地址:\n")
|
||||||
|
if reqCtx == rt.originalCtx {
|
||||||
|
rt.t.Logf("上下文具有相同的内存地址。原始上下文: %p, 请求上下文: %p\n", rt.originalCtx, reqCtx)
|
||||||
|
} else {
|
||||||
|
rt.t.Logf("上下文具有不同的内存地址。原始上下文: %p, 请求上下文: %p\n", rt.originalCtx, reqCtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 context 中的键值对
|
||||||
|
if rt.key != nil {
|
||||||
|
value := reqCtx.Value(rt.key)
|
||||||
|
rt.t.Logf("检查请求上下文中的键 %v:\n", rt.key)
|
||||||
|
if value != rt.expectedVal {
|
||||||
|
rt.t.Errorf("上下文键 %v 的值不匹配: 预期 %v, 实际 %v\n", rt.key, rt.expectedVal, value)
|
||||||
|
} else {
|
||||||
|
rt.t.Logf("上下文键 %v 的值匹配: 预期 %v, 实际 %v\n", rt.key, rt.expectedVal, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查上下文是否已取消
|
||||||
|
select {
|
||||||
|
case <-reqCtx.Done():
|
||||||
|
return nil, reqCtx.Err() // 返回上下文取消错误
|
||||||
|
default:
|
||||||
|
// 返回模拟的 HTTP 响应,包含有效的 JSON
|
||||||
|
responseBody := `{"ticket":"mock-ticket","expires_in":7200}`
|
||||||
|
response := &http.Response{
|
||||||
|
Status: "200 OK",
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Proto: "HTTP/1.1",
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 1,
|
||||||
|
Body: io.NopCloser(bytes.NewReader([]byte(responseBody))),
|
||||||
|
ContentLength: int64(len(responseBody)),
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
response.Header.Set("Content-Type", "application/json")
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// contextKey 定义自定义上下文键类型,避免使用内置 string 类型
|
||||||
|
type contextKey string
|
||||||
|
|
||||||
|
// setupJsInstance 初始化 Js 实例和 HTTP 客户端
|
||||||
|
func setupJsInstance(t *testing.T, ctx context2.Context, key, val interface{}) (*Js, func()) {
|
||||||
|
cfg := &config.Config{
|
||||||
|
AppID: "test-app-id",
|
||||||
|
AppSecret: "test-app-secret",
|
||||||
|
Cache: cache.NewMemory(),
|
||||||
|
}
|
||||||
|
cacheKey := fmt.Sprintf("%s_jsapi_ticket_%s", credential.CacheKeyOfficialAccountPrefix, cfg.AppID)
|
||||||
|
if err := cfg.Cache.Delete(cacheKey); err != nil {
|
||||||
|
t.Fatalf("清除缓存失败: %v", err)
|
||||||
|
}
|
||||||
|
t.Log("清除 jsapi_ticket 的缓存:", cacheKey)
|
||||||
|
|
||||||
|
ctxHandle := &context.Context{Config: cfg, AccessTokenHandle: &mockAccessTokenHandle{}}
|
||||||
|
jsInstance := NewJs(ctxHandle, cfg.AppID)
|
||||||
|
jsInstance.SetJsTicketHandle(credential.NewDefaultJsTicket(cfg.AppID, credential.CacheKeyOfficialAccountPrefix, cfg.Cache))
|
||||||
|
|
||||||
|
originalClient := util.DefaultHTTPClient
|
||||||
|
util.DefaultHTTPClient = &http.Client{
|
||||||
|
Transport: &contextCheckingRoundTripper{originalCtx: ctx, t: t, key: key, expectedVal: val},
|
||||||
|
}
|
||||||
|
return jsInstance, func() { util.DefaultHTTPClient = originalClient }
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetConfigContext 测试GetConfigContext的上下文传递和取消行为。
|
||||||
|
func TestGetConfigContext(t *testing.T) {
|
||||||
|
t.Run("ContextPassing", func(t *testing.T) {
|
||||||
|
ctxKey := contextKey("testKey111") // 使用自定义类型 contextKey
|
||||||
|
ctxValue := "testValue222"
|
||||||
|
ctx := context2.WithValue(context2.Background(), ctxKey, ctxValue)
|
||||||
|
t.Logf("创建的测试上下文: %p, 添加的键值对: %v=%v\n", ctx, ctxKey, ctxValue)
|
||||||
|
|
||||||
|
jsInstance, cleanup := setupJsInstance(t, ctx, ctxKey, ctxValue)
|
||||||
|
defer cleanup()
|
||||||
|
t.Log("调用 GetConfigContext")
|
||||||
|
config2, err := jsInstance.GetConfigContext(ctx, "https://www.baidu.com", "test-app-id")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetConfigContext 失败: %v", err)
|
||||||
|
}
|
||||||
|
if config2.AppID != "test-app-id" {
|
||||||
|
t.Errorf("预期 AppID 为 %s,实际为 %s", "test-app-id", config2.AppID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ContextCancellation", func(t *testing.T) {
|
||||||
|
ctx, cancel := context2.WithCancel(context2.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
jsInstance, cleanup := setupJsInstance(t, ctx, nil, nil)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
cancel()
|
||||||
|
t.Log("调用 GetConfigContext(已取消上下文)")
|
||||||
|
_, err := jsInstance.GetConfigContext(ctx, "https://www.baidu.com", "test-app-id")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("预期上下文取消错误,但 GetConfigContext 未返回错误")
|
||||||
|
} else if !errors.Is(err, context2.Canceled) {
|
||||||
|
t.Errorf("预期错误为 context.Canceled,实际为: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
16
util/http.go
16
util/http.go
@@ -166,6 +166,7 @@ func PostFile(fieldName, filePath, uri string) ([]byte, error) {
|
|||||||
IsFile: true,
|
IsFile: true,
|
||||||
Fieldname: fieldName,
|
Fieldname: fieldName,
|
||||||
FilePath: filePath,
|
FilePath: filePath,
|
||||||
|
Filename: filePath,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return PostMultipartForm(fields, uri)
|
return PostMultipartForm(fields, uri)
|
||||||
@@ -290,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,17 +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 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"`
|
||||||
@@ -444,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
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const (
|
|||||||
clearOptionURL = "https://qyapi.weixin.qq.com/cgi-bin/checkin/clear_checkin_option_array_field?access_token=%s"
|
clearOptionURL = "https://qyapi.weixin.qq.com/cgi-bin/checkin/clear_checkin_option_array_field?access_token=%s"
|
||||||
// delOptionURL 删除打卡规则
|
// delOptionURL 删除打卡规则
|
||||||
delOptionURL = "https://qyapi.weixin.qq.com/cgi-bin/checkin/del_checkin_option?access_token=%s"
|
delOptionURL = "https://qyapi.weixin.qq.com/cgi-bin/checkin/del_checkin_option?access_token=%s"
|
||||||
|
// addRecordURL 添加打卡记录
|
||||||
|
addRecordURL = "https://qyapi.weixin.qq.com/cgi-bin/checkin/add_checkin_record?access_token=%s"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetScheduleListRequest 为打卡人员排班请求
|
// SetScheduleListRequest 为打卡人员排班请求
|
||||||
@@ -140,6 +142,7 @@ type OptionGroupRule struct {
|
|||||||
SyncOutCheckin bool `json:"sync_out_checkin,omitempty"`
|
SyncOutCheckin bool `json:"sync_out_checkin,omitempty"`
|
||||||
BukaRemind OptionGroupBukaRemind `json:"buka_remind,omitempty"`
|
BukaRemind OptionGroupBukaRemind `json:"buka_remind,omitempty"`
|
||||||
BukaRestriction int64 `json:"buka_restriction,omitempty"`
|
BukaRestriction int64 `json:"buka_restriction,omitempty"`
|
||||||
|
CheckinMethodType int64 `json:"checkin_method_type,omitempty"`
|
||||||
SpanDayTime int64 `json:"span_day_time,omitempty"`
|
SpanDayTime int64 `json:"span_day_time,omitempty"`
|
||||||
StandardWorkDuration int64 `json:"standard_work_duration,omitempty"`
|
StandardWorkDuration int64 `json:"standard_work_duration,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -155,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 晚走晚到时间规则信息
|
||||||
@@ -189,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"`
|
||||||
@@ -385,3 +403,41 @@ func (r *Client) DelOption(req *DelOptionRequest) error {
|
|||||||
}
|
}
|
||||||
return util.DecodeWithCommonError(response, "DelOption")
|
return util.DecodeWithCommonError(response, "DelOption")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddRecordRequest 添加打卡记录请求
|
||||||
|
type AddRecordRequest struct {
|
||||||
|
Records []Record `json:"records"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record 打卡记录
|
||||||
|
type Record struct {
|
||||||
|
UserID string `json:"userid"`
|
||||||
|
CheckinTime int64 `json:"checkin_time"`
|
||||||
|
LocationTitle string `json:"location_title"`
|
||||||
|
LocationDetail string `json:"location_detail"`
|
||||||
|
MediaIDS []string `json:"mediaids"`
|
||||||
|
Notes string `json:"notes"`
|
||||||
|
DeviceType int `json:"device_type"`
|
||||||
|
Lat int64 `json:"lat"`
|
||||||
|
Lng int64 `json:"lng"`
|
||||||
|
DeviceDetail string `json:"device_detail"`
|
||||||
|
WifiName string `json:"wifiname"`
|
||||||
|
WifiMac string `json:"wifimac"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRecord 添加打卡记录
|
||||||
|
// see https://developer.work.weixin.qq.com/document/path/99647
|
||||||
|
func (r *Client) AddRecord(req *AddRecordRequest) 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(addRecordURL, accessToken), req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return util.DecodeWithCommonError(response, "AddRecord")
|
||||||
|
}
|
||||||
|
|||||||
@@ -173,9 +173,15 @@ type (
|
|||||||
|
|
||||||
// OtInfo 加班信息
|
// OtInfo 加班信息
|
||||||
OtInfo struct {
|
OtInfo struct {
|
||||||
OtStatus int64 `json:"ot_status"`
|
OtStatus int64 `json:"ot_status"`
|
||||||
OtDuration int64 `json:"ot_duration"`
|
OtDuration int64 `json:"ot_duration"`
|
||||||
ExceptionDuration []uint64 `json:"exception_duration"`
|
ExceptionDuration []uint64 `json:"exception_duration"`
|
||||||
|
WorkdayOverAsVacation int64 `json:"workday_over_as_vacation"`
|
||||||
|
WorkdayOverAsMoney int64 `json:"workday_over_as_money"`
|
||||||
|
RestdayOverAsVacation int64 `json:"restday_over_as_vacation"`
|
||||||
|
RestdayOverAsMoney int64 `json:"restday_over_as_money"`
|
||||||
|
HolidayOverAsVacation int64 `json:"holiday_over_as_vacation"`
|
||||||
|
HolidayOverAsMoney int64 `json:"holiday_over_as_money"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -237,13 +243,20 @@ type (
|
|||||||
RegularDays int64 `json:"regular_days"`
|
RegularDays int64 `json:"regular_days"`
|
||||||
RegularWorkSec int64 `json:"regular_work_sec"`
|
RegularWorkSec int64 `json:"regular_work_sec"`
|
||||||
StandardWorkSec int64 `json:"standard_work_sec"`
|
StandardWorkSec int64 `json:"standard_work_sec"`
|
||||||
|
RestDays int64 `json:"rest_days"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OverWorkInfo 加班情况
|
// OverWorkInfo 加班情况
|
||||||
OverWorkInfo struct {
|
OverWorkInfo struct {
|
||||||
WorkdayOverSec int64 `json:"workday_over_sec"`
|
WorkdayOverSec int64 `json:"workday_over_sec"`
|
||||||
HolidayOverSec int64 `json:"holidays_over_sec"`
|
HolidayOverSec int64 `json:"holidays_over_sec"`
|
||||||
RestDayOverSec int64 `json:"restdays_over_sec"`
|
RestDayOverSec int64 `json:"restdays_over_sec"`
|
||||||
|
WorkdaysOverAsVacation int64 `json:"workdays_over_as_vacation"`
|
||||||
|
WorkdaysOverAsMoney int64 `json:"workdays_over_as_money"`
|
||||||
|
RestdaysOverAsVacation int64 `json:"restdays_over_as_vacation"`
|
||||||
|
RestdaysOverAsMoney int64 `json:"restdays_over_as_money"`
|
||||||
|
HolidaysOverAsVacation int64 `json:"holidays_over_as_vacation"`
|
||||||
|
HolidaysOverAsMoney int64 `json:"holidays_over_as_money"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -304,6 +317,10 @@ type CorpOptionGroup struct {
|
|||||||
BukaRestriction int64 `json:"buka_restriction"`
|
BukaRestriction int64 `json:"buka_restriction"`
|
||||||
ScheduleList []ScheduleList `json:"schedulelist"`
|
ScheduleList []ScheduleList `json:"schedulelist"`
|
||||||
OffWorkIntervalTime int64 `json:"offwork_interval_time"`
|
OffWorkIntervalTime int64 `json:"offwork_interval_time"`
|
||||||
|
SpanDayTime int64 `json:"span_day_time"`
|
||||||
|
StandardWorkDuration int64 `json:"standard_work_duration"`
|
||||||
|
OpenSpCheckin bool `json:"open_sp_checkin"`
|
||||||
|
CheckinMethodType int64 `json:"checkin_method_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GroupCheckinDate 打卡时间,当规则类型为排班时没有意义
|
// GroupCheckinDate 打卡时间,当规则类型为排班时没有意义
|
||||||
@@ -505,6 +522,7 @@ type OptionInfo struct {
|
|||||||
type OptionGroup struct {
|
type OptionGroup struct {
|
||||||
GroupType int64 `json:"grouptype"`
|
GroupType int64 `json:"grouptype"`
|
||||||
GroupID int64 `json:"groupid"`
|
GroupID int64 `json:"groupid"`
|
||||||
|
OpenSpCheckin bool `json:"open_sp_checkin"`
|
||||||
GroupName string `json:"groupname"`
|
GroupName string `json:"groupname"`
|
||||||
CheckinDate []OptionCheckinDate `json:"checkindate"`
|
CheckinDate []OptionCheckinDate `json:"checkindate"`
|
||||||
SpeWorkdays []SpeWorkdays `json:"spe_workdays"`
|
SpeWorkdays []SpeWorkdays `json:"spe_workdays"`
|
||||||
@@ -518,6 +536,10 @@ type OptionGroup struct {
|
|||||||
LocInfos []LocInfos `json:"loc_infos"`
|
LocInfos []LocInfos `json:"loc_infos"`
|
||||||
ScheduleList []ScheduleList `json:"schedulelist"`
|
ScheduleList []ScheduleList `json:"schedulelist"`
|
||||||
BukaRestriction int64 `json:"buka_restriction"`
|
BukaRestriction int64 `json:"buka_restriction"`
|
||||||
|
SpanDayTime int64 `json:"span_day_time"`
|
||||||
|
StandardWorkDuration int64 `json:"standard_work_duration"`
|
||||||
|
OffWorkIntervalTime int64 `json:"offwork_interval_time"`
|
||||||
|
CheckinMethodType int64 `json:"checkin_method_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptionCheckinDate 打卡时间配置
|
// OptionCheckinDate 打卡时间配置
|
||||||
|
|||||||
@@ -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 更新企业已配置的「联系我」方式响应
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ const (
|
|||||||
customerAcquisitionQuotaURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/customer_acquisition_quota?access_token=%s"
|
customerAcquisitionQuotaURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/customer_acquisition_quota?access_token=%s"
|
||||||
// customerAcquisitionStatistic 查询链接使用详情
|
// customerAcquisitionStatistic 查询链接使用详情
|
||||||
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 获取成员多次收消息详情
|
||||||
|
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 (
|
||||||
@@ -66,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 {
|
||||||
@@ -76,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 该获客链接使用范围
|
||||||
@@ -83,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 获客助手--获取获客链接详情
|
||||||
@@ -107,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 {
|
||||||
@@ -140,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 {
|
||||||
@@ -308,3 +325,69 @@ func (r *Client) CustomerAcquisitionStatistic(req *CustomerAcquisitionStatisticR
|
|||||||
err = util.DecodeWithError(response, result, "CustomerAcquisitionStatistic")
|
err = util.DecodeWithError(response, result, "CustomerAcquisitionStatistic")
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// GetChatInfoRequest 获取成员多次收消息详情请求
|
||||||
|
GetChatInfoRequest struct {
|
||||||
|
ChatKey string `json:"chat_key"`
|
||||||
|
}
|
||||||
|
// GetChatInfoResponse 获取成员多次收消息详情响应
|
||||||
|
GetChatInfoResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
UserID string `json:"userid"`
|
||||||
|
ExternalUserID string `json:"external_userid"`
|
||||||
|
ChatInfo ChatInfo `json:"chat_info"`
|
||||||
|
}
|
||||||
|
// ChatInfo 聊天信息
|
||||||
|
ChatInfo struct {
|
||||||
|
RecvMsgCnt int64 `json:"recv_msg_cnt"` // 成员收到的此客户的消息次数
|
||||||
|
LinkID string `json:"link_id"` // 成员添加客户的获客链接id
|
||||||
|
State string `json:"state"` // 成员添加客户的state
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetChatInfo 获取成员多次收消息详情
|
||||||
|
// see https://developer.work.weixin.qq.com/document/path/100130
|
||||||
|
func (r *Client) GetChatInfo(req *GetChatInfoRequest) (*GetChatInfoResponse, 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(customerAcquisitionGetChatInfoURL, accessToken), req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &GetChatInfoResponse{}
|
||||||
|
err = util.DecodeWithError(response, result, "GetChatInfo")
|
||||||
|
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