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

Compare commits

..

8 Commits

Author SHA1 Message Date
houseme
f62fd8de8e Merge branch 'v2' of github.com:silenceper/wechat into feature/remove-redis
# Conflicts:
#	cache/redis.go
2024-08-30 10:46:34 +08:00
houseme
dad35b9f6d Merge branch 'v2' of github.com:silenceper/wechat into feature/remove-redis 2024-05-08 23:11:49 +08:00
houseme
b797312d34 Merge branch 'v2' of github.com:silenceper/wechat into feature/remove-redis 2023-11-02 10:14:52 +08:00
houseme
7bf2053e0f feat: remove redis relation in the action config 2023-09-26 13:15:35 +08:00
houseme
dab9d682cf Merge branch 'v2' of github.com:silenceper/wechat into feature/remove-redis 2023-09-26 13:13:51 +08:00
houseme
b08cf8738e feat: remove redis relation 2023-09-26 13:12:09 +08:00
houseme
e7ff8d90ed fix 2023-08-25 20:05:15 +08:00
houseme
5467bc6245 feat: redis deprecated 2023-08-25 19:57:35 +08:00
59 changed files with 164 additions and 3517 deletions

4
.github/FUNDING.yml vendored
View File

@@ -1,8 +1,8 @@
# These are supported funding model platforms
github: # silenceper
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: gowechat
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
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

View File

@@ -1,38 +0,0 @@
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 }}

View File

@@ -29,11 +29,6 @@ jobs:
name: Test
runs-on: ubuntu-latest
services:
redis:
image: redis
ports:
- 6379:6379
options: --entrypoint redis-server
memcached:
image: memcached
ports:

View File

@@ -1,13 +1,12 @@
# WeChat SDK for Go
![Go](https://github.com/silenceper/wechat/actions/workflows/go.yml/badge.svg?branch=v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/silenceper/wechat/v2)](https://goreportcard.com/report/github.com/silenceper/wechat/v2)
![Go](https://github.com/silenceper/wechat/workflows/Go/badge.svg?branch=release-2.0)
[![Go Report Card](https://goreportcard.com/badge/github.com/silenceper/wechat)](https://goreportcard.com/report/github.com/silenceper/wechat)
[![pkg](https://img.shields.io/badge/dev-reference-007d9c?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/silenceper/wechat/v2?tab=doc)
![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/silenceper/wechat?sort=semver)
![star](https://gitcode.com/silenceper/wechat/star/badge.svg)
![version](https://img.shields.io/badge/version-v2-green)
使用Golang开发的微信SDK简单、易用。
> 注意当前版本为v2版本v1版本已废弃
## 文档 && 例子
@@ -76,13 +75,7 @@ server.Send()
- 提交issue描述需要贡献的内容
- 完成更改后提交PR
## 感谢以下贡献者
<a href="https://opencollective.com/gowechat"><img src="https://opencollective.com/gowechat/contributors.svg?width=890" /></a>
## 作者公众号
## 公众号
![img](https://silenceper.oss-cn-beijing.aliyuncs.com/qrcode/search_study_program.png)

147
cache/redis.go vendored
View File

@@ -1,147 +0,0 @@
package cache
import (
"context"
"crypto/tls"
"net"
"time"
"github.com/go-redis/redis/v8"
)
// Redis .redis cache
type Redis struct {
ctx context.Context
conn redis.UniversalClient
}
// RedisOpts redis 连接属性
type RedisOpts struct {
Host string `json:"host" yaml:"host"`
Username string `json:"username" yaml:"username"`
Password string `json:"password" yaml:"password"`
Database int `json:"database" yaml:"database"`
MinIdleConns int `json:"min_idle_conns" yaml:"min_idle_conns"` // 最小空闲连接数
PoolSize int `json:"pool_size" yaml:"pool_size"` // 连接池大小0 表示使用默认值(即 CPU 核心数 * 10
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 实例化
func NewRedis(ctx context.Context, opts *RedisOpts) *Redis {
uniOpt := &redis.UniversalOptions{
Addrs: []string{opts.Host},
DB: opts.Database,
Username: opts.Username,
Password: opts.Password,
MinIdleConns: opts.MinIdleConns,
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}
}
// SetConn 设置conn
func (r *Redis) SetConn(conn redis.UniversalClient) {
r.conn = conn
}
// SetRedisCtx 设置redis ctx 参数
func (r *Redis) SetRedisCtx(ctx context.Context) {
r.ctx = ctx
}
// Get 获取一个值
func (r *Redis) Get(key string) interface{} {
return r.GetContext(r.ctx, key)
}
// GetContext 获取一个值
func (r *Redis) GetContext(ctx context.Context, key string) interface{} {
result, err := r.conn.Do(ctx, "GET", key).Result()
if err != nil {
return nil
}
return result
}
// Set 设置一个值
func (r *Redis) Set(key string, val interface{}, timeout time.Duration) error {
return r.SetContext(r.ctx, key, val, timeout)
}
// SetContext 设置一个值
func (r *Redis) SetContext(ctx context.Context, key string, val interface{}, timeout time.Duration) error {
return r.conn.SetEX(ctx, key, val, timeout).Err()
}
// IsExist 判断key是否存在
func (r *Redis) IsExist(key string) bool {
return r.IsExistContext(r.ctx, key)
}
// IsExistContext 判断key是否存在
func (r *Redis) IsExistContext(ctx context.Context, key string) bool {
result, _ := r.conn.Exists(ctx, key).Result()
return result > 0
}
// Delete 删除
func (r *Redis) Delete(key string) error {
return r.DeleteContext(r.ctx, key)
}
// DeleteContext 删除
func (r *Redis) DeleteContext(ctx context.Context, key string) error {
return r.conn.Del(ctx, key).Err()
}

312
cache/redis_test.go vendored
View File

@@ -1,312 +0,0 @@
package cache
import (
"context"
"testing"
"time"
"github.com/alicebob/miniredis/v2"
"github.com/go-redis/redis/v8"
)
func TestRedis(t *testing.T) {
server, err := miniredis.Run()
if err != nil {
t.Error("miniredis.Run Error", err)
}
t.Cleanup(server.Close)
var (
timeoutDuration = time.Second
ctx = context.Background()
opts = &RedisOpts{
Host: server.Addr(),
Password: "",
Database: 0,
PoolSize: 10,
MinIdleConns: 5,
DialTimeout: 5,
ReadTimeout: 5,
WriteTimeout: 5,
PoolTimeout: 5,
IdleTimeout: 300,
}
redis = NewRedis(ctx, opts)
val = "silenceper"
key = "username"
)
redis.SetConn(redis.conn)
redis.SetRedisCtx(ctx)
if err = redis.Set(key, val, timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !redis.IsExist(key) {
t.Error("IsExist Error")
}
name := redis.Get(key).(string)
if name != val {
t.Error("get Error")
}
if err = redis.Delete(key); err != nil {
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)
}
}

View File

@@ -7,16 +7,6 @@ type AccessTokenHandle interface {
GetAccessToken() (accessToken string, err error)
}
// AccessTokenCompatibleHandle 同时实现 AccessTokenHandle 和 AccessTokenContextHandle
type AccessTokenCompatibleHandle struct {
AccessTokenHandle
}
// GetAccessTokenContext 获取access_token,先从cache中获取没有则从服务端获取
func (c AccessTokenCompatibleHandle) GetAccessTokenContext(_ context.Context) (accessToken string, err error) {
return c.GetAccessToken()
}
// AccessTokenContextHandle AccessToken 接口
type AccessTokenContextHandle interface {
AccessTokenHandle

View File

@@ -101,11 +101,10 @@ func (ak *DefaultAccessToken) GetAccessTokenContext(ctx context.Context) (access
// 不强制更新access_token,可用于不同环境不同服务而不需要分布式锁以及公用缓存避免access_token争抢
// https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getStableAccessToken.html
type StableAccessToken struct {
appID string
appSecret string
cacheKeyPrefix string
cache cache.Cache
accessTokenLock *sync.Mutex
appID string
appSecret string
cacheKeyPrefix string
cache cache.Cache
}
// NewStableAccessToken new StableAccessToken
@@ -114,11 +113,10 @@ func NewStableAccessToken(appID, appSecret, cacheKeyPrefix string, cache cache.C
panic("cache is need")
}
return &StableAccessToken{
appID: appID,
appSecret: appSecret,
cache: cache,
cacheKeyPrefix: cacheKeyPrefix,
accessTokenLock: new(sync.Mutex),
appID: appID,
appSecret: appSecret,
cache: cache,
cacheKeyPrefix: cacheKeyPrefix,
}
}
@@ -132,20 +130,7 @@ func (ak *StableAccessToken) GetAccessTokenContext(ctx context.Context) (accessT
// 先从cache中取
accessTokenCacheKey := fmt.Sprintf("%s_stable_access_token_%s", ak.cacheKeyPrefix, ak.appID)
if val := ak.cache.Get(accessTokenCacheKey); val != nil {
if accessToken = val.(string); accessToken != "" {
return
}
}
// 加上lock是为了防止在并发获取token时cache刚好失效导致从微信服务器上获取到不同token
ak.accessTokenLock.Lock()
defer ak.accessTokenLock.Unlock()
// 双检,防止重复从微信服务器获取
if val := ak.cache.Get(accessTokenCacheKey); val != nil {
if accessToken = val.(string); accessToken != "" {
return
}
return val.(string), nil
}
// cache失效从微信服务器获取
@@ -189,27 +174,19 @@ func (ak *StableAccessToken) GetAccessTokenDirectly(ctx context.Context, forceRe
type WorkAccessToken struct {
CorpID string
CorpSecret string
AgentID string // 可选,用于区分不同应用
cacheKeyPrefix string
cache cache.Cache
accessTokenLock *sync.Mutex
}
// NewWorkAccessToken new WorkAccessToken (保持向后兼容)
func NewWorkAccessToken(corpID, corpSecret, agentID, cacheKeyPrefix string, cache cache.Cache) AccessTokenContextHandle {
// 调用新方法,保持兼容性
return NewWorkAccessTokenWithAgentID(corpID, corpSecret, agentID, cacheKeyPrefix, cache)
}
// NewWorkAccessTokenWithAgentID new WorkAccessToken with agentID
func NewWorkAccessTokenWithAgentID(corpID, corpSecret, agentID, cacheKeyPrefix string, cache cache.Cache) AccessTokenContextHandle {
// NewWorkAccessToken new WorkAccessToken
func NewWorkAccessToken(corpID, corpSecret, cacheKeyPrefix string, cache cache.Cache) AccessTokenContextHandle {
if cache == nil {
panic("cache is needed")
panic("cache the not exist")
}
return &WorkAccessToken{
CorpID: corpID,
CorpSecret: corpSecret,
AgentID: agentID,
cache: cache,
cacheKeyPrefix: cacheKeyPrefix,
accessTokenLock: new(sync.Mutex),
@@ -226,18 +203,7 @@ func (ak *WorkAccessToken) GetAccessTokenContext(ctx context.Context) (accessTok
// 加上lock是为了防止在并发获取token时cache刚好失效导致从微信服务器上获取到不同token
ak.accessTokenLock.Lock()
defer ak.accessTokenLock.Unlock()
// 构建缓存key
var accessTokenCacheKey string
if ak.AgentID != "" {
// 如果设置了AgentID使用新的key格式
accessTokenCacheKey = fmt.Sprintf("%s_access_token_%s_%s", ak.cacheKeyPrefix, ak.CorpID, ak.AgentID)
} else {
// 兼容历史版本的key格式
accessTokenCacheKey = fmt.Sprintf("%s_access_token_%s", ak.cacheKeyPrefix, ak.CorpID)
}
accessTokenCacheKey := fmt.Sprintf("%s_access_token_%s", ak.cacheKeyPrefix, ak.CorpID)
val := ak.cache.Get(accessTokenCacheKey)
if val != nil {
accessToken = val.(string)
@@ -253,9 +219,6 @@ func (ak *WorkAccessToken) GetAccessTokenContext(ctx context.Context) (accessTok
expires := resAccessToken.ExpiresIn - 1500
err = ak.cache.Set(accessTokenCacheKey, resAccessToken.AccessToken, time.Duration(expires)*time.Second)
if err != nil {
return
}
accessToken = resAccessToken.AccessToken
return

View File

@@ -1,7 +1,6 @@
package credential
import (
context2 "context"
"encoding/json"
"fmt"
"sync"
@@ -43,16 +42,6 @@ type ResTicket struct {
// GetTicket 获取jsapi_ticket
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中取
jsAPITicketCacheKey := fmt.Sprintf("%s_jsapi_ticket_%s", js.cacheKeyPrefix, js.appID)
if val := js.cache.Get(jsAPITicketCacheKey); val != nil {
@@ -68,7 +57,7 @@ func (js *DefaultJsTicket) GetTicketContext(ctx context2.Context, accessToken st
}
var ticket ResTicket
ticket, err = GetTicketFromServerContext(ctx, accessToken)
ticket, err = GetTicketFromServer(accessToken)
if err != nil {
return
}
@@ -78,11 +67,11 @@ func (js *DefaultJsTicket) GetTicketContext(ctx context2.Context, accessToken st
return
}
// GetTicketFromServerContext 从服务器中获取ticket
func GetTicketFromServerContext(ctx context2.Context, accessToken string) (ticket ResTicket, err error) {
// GetTicketFromServer 从服务器中获取ticket
func GetTicketFromServer(accessToken string) (ticket ResTicket, err error) {
var response []byte
url := fmt.Sprintf(getTicketURL, accessToken)
response, err = util.HTTPGetContext(ctx, url)
response, err = util.HTTPGet(url)
if err != nil {
return
}

View File

@@ -1,22 +0,0 @@
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")
}

View File

@@ -1,15 +1,7 @@
package credential
import context2 "context"
// JsTicketHandle js ticket获取
type JsTicketHandle interface {
// GetTicket 获取ticket
GetTicket(accessToken string) (ticket string, err error)
}
// JsTicketContextHandle js ticket获取
type JsTicketContextHandle interface {
JsTicketHandle
GetTicketContext(ctx context2.Context, accessToken string) (ticket string, err error)
}

View File

@@ -1,118 +0,0 @@
package credential
import (
"encoding/json"
"fmt"
"sync"
"time"
"github.com/silenceper/wechat/v2/cache"
"github.com/silenceper/wechat/v2/util"
)
// TicketType ticket类型
type TicketType int
const (
// TicketTypeCorpJs 企业jsapi ticket
TicketTypeCorpJs TicketType = iota
// TicketTypeAgentJs 应用jsapi ticket
TicketTypeAgentJs
)
// 企业微信相关的 ticket URL
const (
// 企业微信 jsapi ticket
getWorkJsTicketURL = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=%s"
// 企业微信应用 jsapi ticket
getWorkAgentJsTicketURL = "https://qyapi.weixin.qq.com/cgi-bin/ticket/get?access_token=%s&type=agent_config"
)
// WorkJsTicket 企业微信js ticket获取
type WorkJsTicket struct {
corpID string
agentID string
cacheKeyPrefix string
cache cache.Cache
jsAPITicketLock *sync.Mutex
}
// NewWorkJsTicket new WorkJsTicket
func NewWorkJsTicket(corpID, agentID, cacheKeyPrefix string, cache cache.Cache) *WorkJsTicket {
return &WorkJsTicket{
corpID: corpID,
agentID: agentID,
cache: cache,
cacheKeyPrefix: cacheKeyPrefix,
jsAPITicketLock: new(sync.Mutex),
}
}
// GetTicket 根据类型获取相应的jsapi_ticket
func (js *WorkJsTicket) GetTicket(accessToken string, ticketType TicketType) (ticketStr string, err error) {
var cacheKey string
switch ticketType {
case TicketTypeCorpJs:
cacheKey = fmt.Sprintf("%s_corp_jsapi_ticket_%s", js.cacheKeyPrefix, js.corpID)
case TicketTypeAgentJs:
if js.agentID == "" {
err = fmt.Errorf("agentID is empty")
return
}
cacheKey = fmt.Sprintf("%s_agent_jsapi_ticket_%s_%s", js.cacheKeyPrefix, js.corpID, js.agentID)
default:
err = fmt.Errorf("unsupported ticket type: %v", ticketType)
return
}
if val := js.cache.Get(cacheKey); val != nil {
return val.(string), nil
}
js.jsAPITicketLock.Lock()
defer js.jsAPITicketLock.Unlock()
// 双检,防止重复从微信服务器获取
if val := js.cache.Get(cacheKey); val != nil {
return val.(string), nil
}
var ticket ResTicket
ticket, err = js.getTicketFromServer(accessToken, ticketType)
if err != nil {
return
}
expires := ticket.ExpiresIn - 1500
err = js.cache.Set(cacheKey, ticket.Ticket, time.Duration(expires)*time.Second)
ticketStr = ticket.Ticket
return
}
// getTicketFromServer 从服务器中获取ticket
func (js *WorkJsTicket) getTicketFromServer(accessToken string, ticketType TicketType) (ticket ResTicket, err error) {
var url string
switch ticketType {
case TicketTypeCorpJs:
url = fmt.Sprintf(getWorkJsTicketURL, accessToken)
case TicketTypeAgentJs:
url = fmt.Sprintf(getWorkAgentJsTicketURL, accessToken)
default:
err = fmt.Errorf("unsupported ticket type: %v", ticketType)
return
}
var response []byte
response, err = util.HTTPGet(url)
if err != nil {
return
}
err = json.Unmarshal(response, &ticket)
if err != nil {
return
}
if ticket.ErrCode != 0 {
err = fmt.Errorf("getTicket Error : errcode=%d , errmsg=%s", ticket.ErrCode, ticket.ErrMsg)
return
}
return
}

2
go.mod
View File

@@ -3,10 +3,8 @@ module github.com/silenceper/wechat/v2
go 1.16
require (
github.com/alicebob/miniredis/v2 v2.30.0
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d
github.com/fatih/structs v1.1.0
github.com/go-redis/redis/v8 v8.11.5
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cast v1.4.1
github.com/stretchr/testify v1.7.1

102
go.sum
View File

@@ -1,61 +1,14 @@
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.30.0 h1:uA3uhDbCxfO9+DI/DuGeAMr9qI+noVWwGPNTFuKID5M=
github.com/alicebob/miniredis/v2 v2.30.0/go.mod h1:84TWKZlxYkfgMucPBf5SOQBYJceZeQRFIaQgNMiCX6Q=
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw=
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
@@ -64,7 +17,6 @@ github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -74,74 +26,20 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -30,9 +30,7 @@ miniprogram := wc.GetMiniProgram(&miniConfig.Config{
AppSecret: "xxx",
AppKey: "xxx",
OfferID: "xxx",
Cache: cache.NewRedis(&redis.Options{
Addr: "",
}),
Cache: cache.NewMemory(),
})
virtualPayment := miniprogram.GetVirtualPayment()
virtualPayment.SetSessionKey("xxx")

View File

@@ -30,8 +30,6 @@ const (
getAnalysisVisitDistributionURL = "https://api.weixin.qq.com/datacube/getweanalysisappidvisitdistribution?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 数据分析
@@ -317,67 +315,3 @@ func (analysis *Analysis) GetAnalysisVisitPage(beginDate, endDate string) (resul
}
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
}

View File

@@ -10,22 +10,11 @@ import (
)
const (
// code2SessionURL 小程序登录
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"
// getPhoneNumber 获取手机号
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 登录/用户信息
@@ -76,45 +65,9 @@ func (auth *Auth) Code2SessionContext(ctx context2.Context, jsCode string) (resu
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无需用户授权
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/basic-info/getPaidUnionid.html
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
func (auth *Auth) GetPaidUnionID() {
// TODO
}
// CheckEncryptedData .检查加密信息是否由微信生成当前只支持手机号加密数据只能检测最近3天生成的加密数据
@@ -128,7 +81,7 @@ func (auth *Auth) CheckEncryptedDataContext(ctx context2.Context, encryptedMsgHa
var (
at string
)
if at, err = auth.GetAccessTokenContext(ctx); err != nil {
if at, err = auth.GetAccessToken(); err != nil {
return
}
@@ -167,7 +120,7 @@ func (auth *Auth) GetPhoneNumberContext(ctx context2.Context, code string) (*Get
at string
err error
)
if at, err = auth.GetAccessTokenContext(ctx); err != nil {
if at, err = auth.GetAccessToken(); err != nil {
return nil, err
}
body := map[string]interface{}{
@@ -193,115 +146,3 @@ func (auth *Auth) GetPhoneNumberContext(ctx context2.Context, code string) (*Get
func (auth *Auth) GetPhoneNumber(code string) (*GetPhoneNumberResponse, error) {
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
}

View File

@@ -1,7 +1,6 @@
package business
import (
"context"
"fmt"
"github.com/silenceper/wechat/v2/util"
@@ -29,18 +28,13 @@ type PhoneInfo struct {
// GetPhoneNumber code换取用户手机号。 每个code只能使用一次code的有效期为5min
func (business *Business) GetPhoneNumber(in *GetPhoneNumberRequest) (info PhoneInfo, err error) {
return business.GetPhoneNumberWithContext(context.Background(), in)
}
// GetPhoneNumberWithContext 利用context将code换取用户手机号。 每个code只能使用一次code的有效期为5min
func (business *Business) GetPhoneNumberWithContext(ctx context.Context, in *GetPhoneNumberRequest) (info PhoneInfo, err error) {
accessToken, err := business.GetAccessTokenContext(ctx)
accessToken, err := business.GetAccessToken()
if err != nil {
return
}
uri := fmt.Sprintf(getPhoneNumberURL, accessToken)
response, err := util.PostJSONContext(ctx, uri, in)
response, err := util.PostJSON(uri, in)
if err != nil {
return
}

View File

@@ -14,5 +14,4 @@ type Config struct {
Token string `json:"token"` // token
EncodingAESKey string `json:"encoding_aes_key"` // EncodingAESKey
Cache cache.Cache
UseStableAK bool // use the stable access_token
}

View File

@@ -8,5 +8,5 @@ import (
// Context struct
type Context struct {
*config.Config
credential.AccessTokenContextHandle
credential.AccessTokenHandle
}

View File

@@ -1,295 +0,0 @@
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
)

View File

@@ -1,16 +0,0 @@
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}
}

View File

@@ -556,7 +556,7 @@ type SubscribeMsgSentEvent struct {
type SubscribeMsgSentList struct {
TemplateID string `xml:"TemplateId" json:"TemplateId"`
MsgID string `xml:"MsgID" json:"MsgID"`
ErrorCode string `xml:"ErrorCode" json:"ErrorCode"`
ErrorCode int `xml:"ErrorCode" json:"ErrorCode"`
ErrorStatus string `xml:"ErrorStatus" json:"ErrorStatus"`
}

View File

@@ -8,12 +8,10 @@ import (
)
const (
// createActivityIDURL 创建activity_id
createActivityIDURL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/activityid/create?access_token=%s&unionid=%s&openid=%s"
// createActivityURL 创建activity_id
createActivityURL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/activityid/create?access_token=%s"
// SendUpdatableMsgURL 修改动态消息
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 动态消息状态
@@ -40,26 +38,15 @@ func NewUpdatableMessage(ctx *context.Context) *UpdatableMessage {
}
}
// CreateActivityIDRequest 创建activity_id请求
type CreateActivityIDRequest struct {
UnionID string
OpenID string
}
// CreateActivityID 创建activity_id
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) {
func (updatableMessage *UpdatableMessage) CreateActivityID() (res CreateActivityIDResponse, err error) {
accessToken, err := updatableMessage.GetAccessToken()
if err != nil {
return
}
url := fmt.Sprintf(createActivityIDURL, accessToken, req.UnionID, req.OpenID)
response, err := util.HTTPGet(url)
uri := fmt.Sprintf(createActivityURL, accessToken)
response, err := util.HTTPGet(uri)
if err != nil {
return
}
@@ -113,35 +100,3 @@ type SendUpdatableMsgReq struct {
TemplateInfo UpdatableMsgTemplate `json:"template_info"`
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")
}

View File

@@ -10,11 +10,8 @@ import (
"github.com/silenceper/wechat/v2/miniprogram/content"
"github.com/silenceper/wechat/v2/miniprogram/context"
"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/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/privacy"
"github.com/silenceper/wechat/v2/miniprogram/qrcode"
@@ -37,30 +34,17 @@ type MiniProgram struct {
// NewMiniProgram 实例化小程序 API
func NewMiniProgram(cfg *config.Config) *MiniProgram {
var defaultAkHandle credential.AccessTokenContextHandle
const cacheKeyPrefix = credential.CacheKeyMiniProgramPrefix
if cfg.UseStableAK {
defaultAkHandle = credential.NewStableAccessToken(cfg.AppID, cfg.AppSecret, cacheKeyPrefix, cfg.Cache)
} else {
defaultAkHandle = credential.NewDefaultAccessToken(cfg.AppID, cfg.AppSecret, cacheKeyPrefix, cfg.Cache)
}
defaultAkHandle := credential.NewDefaultAccessToken(cfg.AppID, cfg.AppSecret, credential.CacheKeyMiniProgramPrefix, cfg.Cache)
ctx := &context.Context{
Config: cfg,
AccessTokenContextHandle: defaultAkHandle,
Config: cfg,
AccessTokenHandle: defaultAkHandle,
}
return &MiniProgram{ctx}
}
// SetAccessTokenHandle 自定义 access_token 获取方式
func (miniProgram *MiniProgram) SetAccessTokenHandle(accessTokenHandle credential.AccessTokenHandle) {
miniProgram.ctx.AccessTokenContextHandle = credential.AccessTokenCompatibleHandle{
AccessTokenHandle: accessTokenHandle,
}
}
// SetAccessTokenContextHandle 自定义 access_token 获取方式
func (miniProgram *MiniProgram) SetAccessTokenContextHandle(accessTokenContextHandle credential.AccessTokenContextHandle) {
miniProgram.ctx.AccessTokenContextHandle = accessTokenContextHandle
miniProgram.ctx.AccessTokenHandle = accessTokenHandle
}
// GetContext get Context
@@ -182,18 +166,3 @@ func (miniProgram *MiniProgram) GetRedPacketCover() *redpacketcover.RedPacketCov
func (miniProgram *MiniProgram) GetUpdatableMessage() *message.UpdatableMessage {
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)
}

View File

@@ -1,248 +0,0 @@
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
}

View File

@@ -1,456 +0,0 @@
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
}

View File

@@ -1,7 +1,6 @@
package security
import (
context2 "context"
"fmt"
"strconv"
@@ -65,12 +64,7 @@ type MediaCheckAsyncRequest struct {
// MediaCheckAsync 异步校验图片/音频是否含有违法违规内容
func (security *Security) MediaCheckAsync(in *MediaCheckAsyncRequest) (traceID string, err error) {
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)
accessToken, err := security.GetAccessToken()
if err != nil {
return
}
@@ -83,7 +77,7 @@ func (security *Security) MediaCheckAsyncContext(ctx context2.Context, in *Media
req.Version = 2
uri := fmt.Sprintf(mediaCheckAsyncURL, accessToken)
response, err := util.PostJSONContext(ctx, uri, req)
response, err := util.PostJSON(uri, req)
if err != nil {
return
}
@@ -228,12 +222,7 @@ func (security *Security) MsgCheckV1(content string) (res MsgCheckResponse, err
// MsgCheck 检查一段文本是否含有违法违规内容
func (security *Security) MsgCheck(in *MsgCheckRequest) (res MsgCheckResponse, err error) {
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)
accessToken, err := security.GetAccessToken()
if err != nil {
return
}
@@ -246,7 +235,7 @@ func (security *Security) MsgCheckContext(ctx context2.Context, in *MsgCheckRequ
req.Version = 2
uri := fmt.Sprintf(msgCheckURL, accessToken)
response, err := util.PostJSONContext(ctx, uri, req)
response, err := util.PostJSON(uri, req)
if err != nil {
return
}

View File

@@ -1,7 +1,6 @@
package subscribe
import (
"encoding/json"
"fmt"
"github.com/silenceper/wechat/v2/miniprogram/context"
@@ -12,30 +11,22 @@ const (
// 发送订阅消息
// 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"
// 获取当前帐号下的个人模板列表
// 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"
// 添加订阅模板
// 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"
// 删除私有模板
// 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"
// 统一服务消息
// 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"
// 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 订阅消息
@@ -66,18 +57,11 @@ type DataItem struct {
// TemplateItem template item
type TemplateItem struct {
PriTmplID string `json:"priTmplId"`
Title string `json:"title"`
Content string `json:"content"`
Example string `json:"example"`
Type int64 `json:"type"`
KeywordEnumValueList []KeywordEnumValue `json:"keywordEnumValueList"`
}
// KeywordEnumValue 枚举参数值范围
type KeywordEnumValue struct {
EnumValueList []string `json:"enumValueList"`
KeywordCode string `json:"keywordCode"`
PriTmplID string `json:"priTmplId"`
Title string `json:"title"`
Content string `json:"content"`
Example string `json:"example"`
Type int64 `json:"type"`
}
// TemplateList template list
@@ -86,13 +70,6 @@ type TemplateList struct {
Data []TemplateItem `json:"data"`
}
// resTemplateSend 发送获取 msg id
type resTemplateSend struct {
util.CommonError
MsgID int64 `json:"msgid"`
}
// Send 发送订阅消息
func (s *Subscribe) Send(msg *Message) (err error) {
var accessToken string
@@ -108,33 +85,6 @@ func (s *Subscribe) Send(msg *Message) (err error) {
return util.DecodeWithCommonError(response, "Send")
}
// SendGetMsgID 发送订阅消息返回 msgid
func (s *Subscribe) SendGetMsgID(msg *Message) (msgID int64, err error) {
var accessToken string
accessToken, err = s.GetAccessToken()
if err != nil {
return
}
uri := fmt.Sprintf("%s?access_token=%s", subscribeSendURL, accessToken)
response, err := util.PostJSON(uri, msg)
if err != nil {
return
}
var result resTemplateSend
if err = json.Unmarshal(response, &result); err != nil {
return
}
if result.ErrCode != 0 {
err = fmt.Errorf("template msg send error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
return
}
msgID = result.MsgID
return
}
// ListTemplates 获取当前帐号下的个人模板列表
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.getTemplateList.html
func (s *Subscribe) ListTemplates() (*TemplateList, error) {
@@ -239,200 +189,3 @@ func (s *Subscribe) Delete(templateID string) (err error) {
}
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
}

View File

@@ -6,13 +6,7 @@ import (
"github.com/silenceper/wechat/v2/util"
)
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"`
}
const queryURL = "https://api.weixin.qq.com/wxa/query_urllink"
// ULQueryResult 返回的结果
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-link/urllink.query.html 返回值
@@ -34,35 +28,25 @@ type ULQueryResult struct {
ResourceAppid string `json:"resource_appid"`
} `json:"cloud_base"`
} `json:"url_link_info"`
VisitOpenid string `json:"visit_openid"`
QuotaInfo QuotaInfo `json:"quota_info"`
}
// QuotaInfo quota 配置
type QuotaInfo struct {
RemainVisitQuota int64 `json:"remain_visit_quota"`
VisitOpenid string `json:"visit_openid"`
}
// Query 查询小程序 url_link 配置。
func (u *URLLink) Query(urlLink string) (*ULQueryResult, error) {
return u.QueryWithType(&ULQueryRequest{URLLink: urlLink})
}
accessToken, err := u.GetAccessToken()
if err != nil {
return nil, err
}
// QueryWithType 查询加密URLLink
// see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/url-link/queryUrlLink.html
func (u *URLLink) QueryWithType(req *ULQueryRequest) (*ULQueryResult, error) {
var (
accessToken string
err error
)
if accessToken, err = u.GetAccessToken(); err != nil {
uri := fmt.Sprintf("%s?access_token=%s", queryURL, accessToken)
response, err := util.PostJSON(uri, map[string]string{"url_link": urlLink})
if err != nil {
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(queryURL, accessToken), req); err != nil {
var resp ULQueryResult
err = util.DecodeWithError(response, &resp, "URLLink.Query")
if err != nil {
return nil, err
}
result := &ULQueryResult{}
err = util.DecodeWithError(response, result, "URLLink.Query")
return result, err
return &resp, nil
}

View File

@@ -14,8 +14,7 @@ const (
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-scheme/urlscheme.query.html#参数
type QueryScheme struct {
// 小程序 scheme 码
Scheme string `json:"scheme"`
QueryType int `json:"query_type"`
Scheme string `json:"scheme"`
}
// SchemeInfo scheme 配置
@@ -34,47 +33,34 @@ type SchemeInfo struct {
EnvVersion EnvVersion `json:"env_version"`
}
// QuotaInfo quota 配置
type QuotaInfo struct {
RemainVisitQuota int64 `json:"remain_visit_quota"`
}
// ResQueryScheme 返回结构体
// resQueryScheme 返回结构体
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-scheme/urlscheme.query.html#参数
type ResQueryScheme struct {
type resQueryScheme struct {
// 通用错误
util.CommonError
// scheme 配置
SchemeInfo SchemeInfo `json:"scheme_info"`
// 访问该链接的openid没有用户访问过则为空字符串
VisitOpenid string `json:"visit_openid"`
QuotaInfo QuotaInfo `json:"quota_info"`
VisitOpenid string `json:"visit_openid"`
}
// QueryScheme 查询小程序 scheme 码
func (u *URLScheme) QueryScheme(querySchemeParams QueryScheme) (schemeInfo SchemeInfo, visitOpenid string, err error) {
res, err := u.QuerySchemeWithRes(querySchemeParams)
var accessToken string
accessToken, err = u.GetAccessToken()
if err != nil {
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
}
// 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
}

View File

@@ -17,12 +17,7 @@ func NewURLScheme(ctx *context.Context) *URLScheme {
return &URLScheme{Context: ctx}
}
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"
)
const generateURL = "https://api.weixin.qq.com/wxa/generatescheme"
// TExpireType 失效类型 (指定时间戳/指定间隔)
type TExpireType int
@@ -55,13 +50,10 @@ type JumpWxa struct {
// USParams 请求参数
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-scheme/urlscheme.generate.html#请求参数
type USParams struct {
JumpWxa *JumpWxa `json:"jump_wxa,omitempty"`
ExpireType TExpireType `json:"expire_type,omitempty"`
ExpireTime int64 `json:"expire_time,omitempty"`
ExpireInterval int `json:"expire_interval,omitempty"`
IsExpire bool `json:"is_expire,omitempty"`
ModelID string `json:"model_id,omitempty"`
Sn string `json:"sn,omitempty"`
JumpWxa *JumpWxa `json:"jump_wxa"`
ExpireType TExpireType `json:"expire_type"`
ExpireTime int64 `json:"expire_time"`
ExpireInterval int `json:"expire_interval"`
}
// USResult 返回的结果
@@ -88,22 +80,3 @@ func (u *URLScheme) Generate(params *USParams) (string, error) {
err = util.DecodeWithError(response, &resp, "URLScheme.Generate")
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
}

View File

@@ -11,5 +11,4 @@ type Config struct {
Token string `json:"token"` // token
EncodingAESKey string `json:"encoding_aes_key"` // EncodingAESKey
Cache cache.Cache
UseStableAK bool // use the stable access_token
}

View File

@@ -1,7 +1,6 @@
package js
import (
context2 "context"
"fmt"
"github.com/silenceper/wechat/v2/credential"
@@ -40,31 +39,14 @@ func (js *Js) SetJsTicketHandle(ticketHandle credential.JsTicketHandle) {
// GetConfig 获取jssdk需要的配置参数
// uri 为当前网页地址
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) {
config = new(Config)
var accessToken string
// 类型断言,如果断言成功,调用安全的 GetAccessTokenContext 方法
if ctxHandle, ok := js.Context.AccessTokenHandle.(credential.AccessTokenContextHandle); ok {
accessToken, err = ctxHandle.GetAccessTokenContext(ctx)
} else {
// 如果没有实现 AccessTokenContextHandle 接口,调用旧的 GetAccessToken 方法
accessToken, err = js.Context.GetAccessToken()
}
accessToken, err = js.GetAccessToken()
if err != nil {
return
}
var ticketStr string
// 类型断言 jsTicket
if ticketCtxHandle, ok := js.JsTicketHandle.(credential.JsTicketContextHandle); ok {
ticketStr, err = ticketCtxHandle.GetTicketContext(ctx, accessToken)
} else {
// 如果没有实现 JsTicketContextHandle 接口,调用旧的 GetTicket 方法
ticketStr, err = js.GetTicket(accessToken)
}
ticketStr, err = js.GetTicket(accessToken)
if err != nil {
return
}
@@ -74,7 +56,6 @@ func (js *Js) GetConfigContext(ctx context2.Context, uri string) (config *Config
str := fmt.Sprintf("jsapi_ticket=%s&noncestr=%s&timestamp=%d&url=%s", ticketStr, nonceStr, timestamp, uri)
sigStr := util.Signature(str)
config = new(Config)
config.AppID = js.AppID
config.NonceStr = nonceStr
config.Timestamp = timestamp

View File

@@ -49,13 +49,7 @@ type OfficialAccount struct {
// NewOfficialAccount 实例化公众号API
func NewOfficialAccount(cfg *config.Config) *OfficialAccount {
var defaultAkHandle credential.AccessTokenContextHandle
const cacheKeyPrefix = credential.CacheKeyOfficialAccountPrefix
if cfg.UseStableAK {
defaultAkHandle = credential.NewStableAccessToken(cfg.AppID, cfg.AppSecret, cacheKeyPrefix, cfg.Cache)
} else {
defaultAkHandle = credential.NewDefaultAccessToken(cfg.AppID, cfg.AppSecret, cacheKeyPrefix, cfg.Cache)
}
defaultAkHandle := credential.NewDefaultAccessToken(cfg.AppID, cfg.AppSecret, credential.CacheKeyOfficialAccountPrefix, cfg.Cache)
ctx := &context.Context{
Config: cfg,
AccessTokenHandle: defaultAkHandle,

View File

@@ -2,7 +2,7 @@ package account
import "github.com/silenceper/wechat/v2/openplatform/context"
// Account 开放平台帐号管理
// Account 开放平台张哈管理
// TODO 实现方法
type Account struct {
*context.Context

View File

@@ -20,7 +20,6 @@ const (
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"
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 获取授权方选项信息
// getComponentConfigURL = "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_option?component_access_token=%s"
// TODO 获取已授权的账号信息
@@ -138,20 +137,6 @@ func (ctx *Context) GetBindComponentURL(redirectURI string, authType int, bizApp
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 微信返回接口中各种类型字段
type ID struct {
ID int `json:"id"`
@@ -240,10 +225,6 @@ 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 {
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
}
@@ -257,18 +238,8 @@ func (ctx *Context) GetAuthrAccessTokenContext(stdCtx context.Context, appid str
authrTokenKey := "authorizer_access_token_" + appid
val := cache.GetContext(stdCtx, ctx.Cache, authrTokenKey)
if val == nil {
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 "", fmt.Errorf("cannot get authorizer %s access token", appid)
}
return val.(string), nil
}

View File

@@ -1,7 +1,6 @@
package miniprogram
import (
originalContext "context"
"fmt"
"github.com/silenceper/wechat/v2/credential"
@@ -38,22 +37,6 @@ func (miniProgram *MiniProgram) GetAccessToken() (string, error) {
return akRes.AccessToken, nil
}
// GetAccessTokenContext 利用ctx获取ak
func (miniProgram *MiniProgram) GetAccessTokenContext(ctx originalContext.Context) (string, error) {
ak, akErr := miniProgram.openContext.GetAuthrAccessTokenContext(ctx, miniProgram.AppID)
if akErr == nil {
return ak, nil
}
if miniProgram.authorizerRefreshToken == "" {
return "", fmt.Errorf("please set the authorizer_refresh_token first")
}
akRes, akResErr := miniProgram.GetComponent().RefreshAuthrTokenContext(ctx, miniProgram.AppID, miniProgram.authorizerRefreshToken)
if akResErr != nil {
return "", akResErr
}
return akRes.AccessToken, nil
}
// SetAuthorizerRefreshToken 设置代执操作业务授权账号authorizer_refresh_token
func (miniProgram *MiniProgram) SetAuthorizerRefreshToken(authorizerRefreshToken string) *MiniProgram {
miniProgram.authorizerRefreshToken = authorizerRefreshToken
@@ -85,7 +68,7 @@ func (miniProgram *MiniProgram) GetBasic() *basic.Basic {
// GetURLLink 小程序URL Link接口 调用前需确认已调用 SetAuthorizerRefreshToken 避免由于缓存中 authorizer_access_token 过期执行中断
func (miniProgram *MiniProgram) GetURLLink() *urllink.URLLink {
return urllink.NewURLLink(&miniContext.Context{
AccessTokenContextHandle: miniProgram,
AccessTokenHandle: miniProgram,
})
}

View File

@@ -1,7 +1,6 @@
package js
import (
context2 "context"
"fmt"
"github.com/silenceper/wechat/v2/credential"
@@ -33,31 +32,14 @@ func (js *Js) SetJsTicketHandle(ticketHandle credential.JsTicketHandle) {
// GetConfig 第三方平台 - 获取jssdk需要的配置参数
// uri 为当前网页地址
func (js *Js) GetConfig(uri, appid string) (config *officialJs.Config, err error) {
return js.GetConfigContext(context2.Background(), uri, appid)
}
// GetConfigContext 新方法,允许传入上下文,避免协程泄漏
func (js *Js) GetConfigContext(ctx context2.Context, uri, appid string) (config *officialJs.Config, err error) {
config = new(officialJs.Config)
var accessToken string
// 类型断言,如果断言成功,调用安全的 GetAccessTokenContext 方法
if ctxHandle, ok := js.Context.AccessTokenHandle.(credential.AccessTokenContextHandle); ok {
accessToken, err = ctxHandle.GetAccessTokenContext(ctx)
} else {
// 如果没有实现 AccessTokenContextHandle 接口,调用旧的 GetAccessToken 方法
accessToken, err = js.Context.GetAccessToken()
}
accessToken, err = js.GetAccessToken()
if err != nil {
return
}
var ticketStr string
// 类型断言 jsTicket
if ticketCtxHandle, ok := js.JsTicketHandle.(credential.JsTicketContextHandle); ok {
ticketStr, err = ticketCtxHandle.GetTicketContext(ctx, accessToken)
} else {
// 如果没有实现 JsTicketContextHandle 接口,调用旧的 GetTicket 方法
ticketStr, err = js.GetTicket(accessToken)
}
ticketStr, err = js.GetTicket(accessToken)
if err != nil {
return
}
@@ -67,7 +49,6 @@ func (js *Js) GetConfigContext(ctx context2.Context, uri, appid string) (config
str := fmt.Sprintf("jsapi_ticket=%s&noncestr=%s&timestamp=%d&url=%s", ticketStr, nonceStr, timestamp, uri)
sigStr := util.Signature(str)
config = new(officialJs.Config)
config.AppID = appid
config.NonceStr = nonceStr
config.Timestamp = timestamp

View File

@@ -1,147 +0,0 @@
// 验证 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)
}
})
}

View File

@@ -68,18 +68,3 @@ func DecodeWithError(response []byte, obj interface{}, apiName string) error {
}
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
}

View File

@@ -166,7 +166,6 @@ func PostFile(fieldName, filePath, uri string) ([]byte, error) {
IsFile: true,
Fieldname: fieldName,
FilePath: filePath,
Filename: filePath,
},
}
return PostMultipartForm(fields, uri)
@@ -291,20 +290,7 @@ func httpWithTLS(rootCa, key string) (*http.Client, error) {
config := &tls.Config{
Certificates: []tls.Certificate{cert},
}
// 安全地获取 *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 := (DefaultHTTPClient.Transport.(*http.Transport)).Clone()
trans.TLSClientConfig = config
trans.DisableCompression = true
client = &http.Client{Transport: trans}

View File

@@ -1,81 +0,0 @@
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")
}

View File

@@ -9,16 +9,12 @@ import (
const (
// departmentCreateURL 创建部门
departmentCreateURL = "https://qyapi.weixin.qq.com/cgi-bin/department/create?access_token=%s"
// departmentUpdateURL 更新部门
departmentUpdateURL = "https://qyapi.weixin.qq.com/cgi-bin/department/update?access_token=%s"
// departmentDeleteURL 删除部门
departmentDeleteURL = "https://qyapi.weixin.qq.com/cgi-bin/department/delete?access_token=%s&id=%d"
// departmentSimpleListURL 获取子部门ID列表
departmentSimpleListURL = "https://qyapi.weixin.qq.com/cgi-bin/department/simplelist?access_token=%s&id=%d"
// departmentListURL 获取部门列表
departmentListURL = "https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token=%s"
departmentListByIDURL = "https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token=%s&id=%d"
// departmentGetURL 获取单个部门详情
// departmentGetURL 获取单个部门详情 https://qyapi.weixin.qq.com/cgi-bin/department/get?access_token=ACCESS_TOKEN&id=ID
departmentGetURL = "https://qyapi.weixin.qq.com/cgi-bin/department/get?access_token=%s&id=%d"
)
@@ -89,49 +85,6 @@ func (r *Client) DepartmentCreate(req *DepartmentCreateRequest) (*DepartmentCrea
return result, err
}
// DepartmentUpdateRequest 更新部门请求
type DepartmentUpdateRequest struct {
ID int `json:"id"`
Name string `json:"name,omitempty"`
NameEn string `json:"name_en,omitempty"`
ParentID int `json:"parentid,omitempty"`
Order int `json:"order,omitempty"`
}
// DepartmentUpdate 更新部门
// see https://developer.work.weixin.qq.com/document/path/90206
func (r *Client) DepartmentUpdate(req *DepartmentUpdateRequest) 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(departmentUpdateURL, accessToken), req); err != nil {
return err
}
return util.DecodeWithCommonError(response, "DepartmentUpdate")
}
// DepartmentDelete 删除部门
// @see https://developer.work.weixin.qq.com/document/path/90207
func (r *Client) DepartmentDelete(departmentID int) 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(departmentDeleteURL, accessToken, departmentID)); err != nil {
return err
}
return util.DecodeWithCommonError(response, "DepartmentDelete")
}
// DepartmentSimpleList 获取子部门ID列表
// see https://developer.work.weixin.qq.com/document/path/95350
func (r *Client) DepartmentSimpleList(departmentID int) ([]*DepartmentID, error) {

View File

@@ -12,8 +12,6 @@ const (
userSimpleListURL = "https://qyapi.weixin.qq.com/cgi-bin/user/simplelist"
// userCreateURL 创建成员
userCreateURL = "https://qyapi.weixin.qq.com/cgi-bin/user/create?access_token=%s"
// userUpdateURL 更新成员
userUpdateURL = "https://qyapi.weixin.qq.com/cgi-bin/user/update?access_token=%s"
// userGetURL 读取成员
userGetURL = "https://qyapi.weixin.qq.com/cgi-bin/user/get"
// userDeleteURL 删除成员
@@ -24,14 +22,6 @@ const (
convertToOpenIDURL = "https://qyapi.weixin.qq.com/cgi-bin/user/convert_to_openid"
// convertToUserIDURL openID转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"
)
type (
@@ -164,54 +154,6 @@ func (r *Client) UserCreate(req *UserCreateRequest) (*UserCreateResponse, error)
return result, err
}
// UserUpdateRequest 更新成员请求
type UserUpdateRequest struct {
UserID string `json:"userid"`
NewUserID string `json:"new_userid"`
Name string `json:"name"`
Alias string `json:"alias"`
Mobile string `json:"mobile"`
Department []int `json:"department"`
Order []int `json:"order"`
Position string `json:"position"`
Gender int `json:"gender"`
Email string `json:"email"`
BizMail string `json:"biz_mail"`
BizMailAlias struct {
Item []string `json:"item"`
} `json:"biz_mail_alias"`
IsLeaderInDept []int `json:"is_leader_in_dept"`
DirectLeader []string `json:"direct_leader"`
Enable int `json:"enable"`
AvatarMediaid string `json:"avatar_mediaid"`
Telephone string `json:"telephone"`
Address string `json:"address"`
MainDepartment int `json:"main_department"`
Extattr struct {
Attrs []ExtraAttr `json:"attrs"`
} `json:"extattr"`
ToInvite bool `json:"to_invite"`
ExternalPosition string `json:"external_position"`
ExternalProfile ExternalProfile `json:"external_profile"`
}
// UserUpdate 更新成员
// see https://developer.work.weixin.qq.com/document/path/90197
func (r *Client) UserUpdate(req *UserUpdateRequest) 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(userUpdateURL, accessToken), req); err != nil {
return err
}
return util.DecodeWithCommonError(response, "UserUpdate")
}
// UserGetResponse 获取部门成员响应
type UserGetResponse struct {
util.CommonError
@@ -455,112 +397,3 @@ func (r *Client) ConvertToUserID(openID string) (string, error) {
err = util.DecodeWithError(response, result, "ConvertToUserID")
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
}

View File

@@ -21,8 +21,6 @@ const (
clearOptionURL = "https://qyapi.weixin.qq.com/cgi-bin/checkin/clear_checkin_option_array_field?access_token=%s"
// delOptionURL 删除打卡规则
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 为打卡人员排班请求
@@ -142,7 +140,6 @@ type OptionGroupRule struct {
SyncOutCheckin bool `json:"sync_out_checkin,omitempty"`
BukaRemind OptionGroupBukaRemind `json:"buka_remind,omitempty"`
BukaRestriction int64 `json:"buka_restriction,omitempty"`
CheckinMethodType int64 `json:"checkin_method_type,omitempty"`
SpanDayTime int64 `json:"span_day_time,omitempty"`
StandardWorkDuration int64 `json:"standard_work_duration,omitempty"`
}
@@ -158,32 +155,24 @@ type OptionGroupRuleCheckinDate struct {
MaxAllowArriveEarly int64 `json:"max_allow_arrive_early"`
MaxAllowArriveLate int64 `json:"max_allow_arrive_late"`
LateRule OptionGroupLateRule `json:"late_rule"`
Biweekly OptionGroupBiweekly `json:"biweekly,omitempty"`
}
// OptionGroupRuleCheckinTime 工作日上下班打卡时间信息
type OptionGroupRuleCheckinTime struct {
TimeID int64 `json:"time_id"`
WorkSec int64 `json:"work_sec"`
OffWorkSec int64 `json:"off_work_sec"`
RemindWorkSec int64 `json:"remind_work_sec"`
RemindOffWorkSec int64 `json:"remind_off_work_sec"`
AllowRest bool `json:"allow_rest"`
RestBeginTime int64 `json:"rest_begin_time"`
RestEndTime int64 `json:"rest_end_time"`
EarliestWorkSec int64 `json:"earliest_work_sec"`
LatestWorkSec int64 `json:"latest_work_sec"`
EarliestOffWorkSec int64 `json:"earliest_off_work_sec"`
LatestOffWorkSec int64 `json:"latest_off_work_sec"`
NoNeedCheckOn bool `json:"no_need_checkon"`
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"`
TimeID int64 `json:"time_id"`
WorkSec int64 `json:"work_sec"`
OffWorkSec int64 `json:"off_work_sec"`
RemindWorkSec int64 `json:"remind_work_sec"`
RemindOffWorkSec int64 `json:"remind_off_work_sec"`
AllowRest bool `json:"allow_rest"`
RestBeginTime int64 `json:"rest_begin_time"`
RestEndTime int64 `json:"rest_end_time"`
EarliestWorkSec int64 `json:"earliest_work_sec"`
LatestWorkSec int64 `json:"latest_work_sec"`
EarliestOffWorkSec int64 `json:"earliest_off_work_sec"`
LatestOffWorkSec int64 `json:"latest_off_work_sec"`
NoNeedCheckOn bool `json:"no_need_checkon"`
NoNeedCheckOff bool `json:"no_need_checkoff"`
}
// OptionGroupLateRule 晚走晚到时间规则信息
@@ -200,13 +189,6 @@ type OptionGroupTimeRule struct {
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 特殊工作日
type OptionGroupSpeWorkdays struct {
Timestamp int64 `json:"timestamp"`
@@ -403,41 +385,3 @@ func (r *Client) DelOption(req *DelOptionRequest) error {
}
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")
}

View File

@@ -173,15 +173,9 @@ type (
// OtInfo 加班信息
OtInfo struct {
OtStatus int64 `json:"ot_status"`
OtDuration int64 `json:"ot_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"`
OtStatus int64 `json:"ot_status"`
OtDuration int64 `json:"ot_duration"`
ExceptionDuration []uint64 `json:"exception_duration"`
}
)
@@ -243,20 +237,13 @@ type (
RegularDays int64 `json:"regular_days"`
RegularWorkSec int64 `json:"regular_work_sec"`
StandardWorkSec int64 `json:"standard_work_sec"`
RestDays int64 `json:"rest_days"`
}
// OverWorkInfo 加班情况
OverWorkInfo struct {
WorkdayOverSec int64 `json:"workday_over_sec"`
HolidayOverSec int64 `json:"holidays_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"`
WorkdayOverSec int64 `json:"workday_over_sec"`
HolidayOverSec int64 `json:"holidays_over_sec"`
RestDayOverSec int64 `json:"restdays_over_sec"`
}
)
@@ -317,10 +304,6 @@ type CorpOptionGroup struct {
BukaRestriction int64 `json:"buka_restriction"`
ScheduleList []ScheduleList `json:"schedulelist"`
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 打卡时间,当规则类型为排班时没有意义
@@ -522,7 +505,6 @@ type OptionInfo struct {
type OptionGroup struct {
GroupType int64 `json:"grouptype"`
GroupID int64 `json:"groupid"`
OpenSpCheckin bool `json:"open_sp_checkin"`
GroupName string `json:"groupname"`
CheckinDate []OptionCheckinDate `json:"checkindate"`
SpeWorkdays []SpeWorkdays `json:"spe_workdays"`
@@ -536,10 +518,6 @@ type OptionGroup struct {
LocInfos []LocInfos `json:"loc_infos"`
ScheduleList []ScheduleList `json:"schedulelist"`
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 打卡时间配置

View File

@@ -7,11 +7,12 @@ import (
// Config for 企业微信
type Config struct {
CorpID string `json:"corp_id"` // corp_id
CorpSecret string `json:"corp_secret"` // corp_secret,如果需要获取会话存档实例当前参数请填写聊天内容存档的Secret可以在企业微信管理端--管理工具--聊天内容存档查看
AgentID string `json:"agent_id"` // agent_id
Cache cache.Cache
RasPrivateKey string // 消息加密私钥,可以在企业微信管理端--管理工具--消息加密公钥查看对用公钥,私钥一般由自己保存
CorpID string `json:"corp_id"` // corp_id
CorpSecret string `json:"corp_secret"` // corp_secret,如果需要获取会话存档实例当前参数请填写聊天内容存档的Secret可以在企业微信管理端--管理工具--聊天内容存档查看
AgentID string `json:"agent_id"` // agent_id
Cache cache.Cache
RasPrivateKey string // 消息加密私钥,可以在企业微信管理端--管理工具--消息加密公钥查看对用公钥,私钥一般由自己保存
Token string `json:"token"` // 微信客服回调配置,用于生成签名校验回调请求的合法性
EncodingAESKey string `json:"encoding_aes_key"` // 微信客服回调p配置用于解密回调消息内容对应的密文
}

View File

@@ -77,8 +77,6 @@ type (
ExpiresIn int `json:"expires_in"`
ChatExpiresIn int `json:"chat_expires_in"`
UnionID string `json:"unionid"`
IsExclusive bool `json:"is_exclusive"`
MarkSource bool `json:"mark_source"`
Conclusions ConclusionsRequest `json:"conclusions"`
}
// AddContactWayResponse 配置客户联系「联系我」方式响应
@@ -134,7 +132,6 @@ type (
ExpiresIn int `json:"expires_in"`
ChatExpiresIn int `json:"chat_expires_in"`
UnionID string `json:"unionid"`
MarkSource bool `json:"mark_source"`
Conclusions ConclusionsResponse `json:"conclusions"`
}
)
@@ -171,7 +168,6 @@ type (
ExpiresIn int `json:"expires_in"`
ChatExpiresIn int `json:"chat_expires_in"`
UnionID string `json:"unionid"`
MarkSource bool `json:"mark_source"`
Conclusions ConclusionsRequest `json:"conclusions"`
}
// UpdateContactWayResponse 更新企业已配置的「联系我」方式响应

View File

@@ -23,10 +23,6 @@ const (
customerAcquisitionQuotaURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/customer_acquisition_quota?access_token=%s"
// customerAcquisitionStatistic 查询链接使用详情
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 (
@@ -70,10 +66,9 @@ type (
// GetCustomerAcquisitionResponse 获取获客链接详情响应
GetCustomerAcquisitionResponse struct {
util.CommonError
Link Link `json:"link"`
Range CustomerAcquisitionRange `json:"range"`
PriorityOption CustomerPriorityOption `json:"priority_option"`
SkipVerify bool `json:"skip_verify"`
Link Link `json:"link"`
Range CustomerAcquisitionRange `json:"range"`
SkipVerify bool `json:"skip_verify"`
}
// Link 获客链接
Link struct {
@@ -81,8 +76,6 @@ type (
LinkName string `json:"link_name"`
URL string `json:"url"`
CreateTime int64 `json:"create_time"`
SkipVerify bool `json:"skip_verify"`
MarkSource bool `json:"mark_source"`
}
// CustomerAcquisitionRange 该获客链接使用范围
@@ -90,12 +83,6 @@ type (
UserList []string `json:"user_list"`
DepartmentList []int64 `json:"department_list"`
}
// CustomerPriorityOption 该获客链接的优先选项
CustomerPriorityOption struct {
PriorityType int `json:"priority_type"`
PriorityUseridList []string `json:"priority_userid_list"`
}
)
// GetCustomerAcquisition 获客助手--获取获客链接详情
@@ -120,11 +107,9 @@ func (r *Client) GetCustomerAcquisition(req *GetCustomerAcquisitionRequest) (*Ge
type (
// CreateCustomerAcquisitionLinkRequest 创建获客链接请求
CreateCustomerAcquisitionLinkRequest struct {
LinkName string `json:"link_name"`
Range CustomerAcquisitionRange `json:"range"`
SkipVerify bool `json:"skip_verify"`
PriorityOption CustomerPriorityOption `json:"priority_option"`
MarkSource bool `json:"mark_source"`
LinkName string `json:"link_name"`
Range CustomerAcquisitionRange `json:"range"`
SkipVerify bool `json:"skip_verify"`
}
// CreateCustomerAcquisitionLinkResponse 创建获客链接响应
CreateCustomerAcquisitionLinkResponse struct {
@@ -155,12 +140,10 @@ func (r *Client) CreateCustomerAcquisitionLink(req *CreateCustomerAcquisitionLin
type (
// UpdateCustomerAcquisitionLinkRequest 编辑获客链接请求
UpdateCustomerAcquisitionLinkRequest struct {
LinkID string `json:"link_id"`
LinkName string `json:"link_name"`
Range CustomerAcquisitionRange `json:"range"`
SkipVerify bool `json:"skip_verify"`
PriorityOption CustomerPriorityOption `json:"priority_option"`
MarkSource bool `json:"mark_source"`
LinkID string `json:"link_id"`
LinkName string `json:"link_name"`
Range CustomerAcquisitionRange `json:"range"`
SkipVerify bool `json:"skip_verify"`
}
// UpdateCustomerAcquisitionLinkResponse 编辑获客链接响应
UpdateCustomerAcquisitionLinkResponse struct {
@@ -325,69 +308,3 @@ func (r *Client) CustomerAcquisitionStatistic(req *CustomerAcquisitionStatisticR
err = util.DecodeWithError(response, result, "CustomerAcquisitionStatistic")
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
}

View File

@@ -176,7 +176,6 @@ type BatchGetExternalUserDetailsRequest struct {
type ExternalUserDetailListResponse struct {
util.CommonError
ExternalContactList []ExternalUserForBatch `json:"external_contact_list"`
NextCursor string `json:"next_cursor"`
}
// ExternalUserForBatch 批量获取外部联系人客户列表
@@ -215,23 +214,23 @@ type FollowInfo struct {
// BatchGetExternalUserDetails 批量获取外部联系人详情
// @see https://developer.work.weixin.qq.com/document/path/92994
func (r *Client) BatchGetExternalUserDetails(request BatchGetExternalUserDetailsRequest) ([]ExternalUserForBatch, string, error) {
func (r *Client) BatchGetExternalUserDetails(request BatchGetExternalUserDetailsRequest) ([]ExternalUserForBatch, error) {
accessToken, err := r.GetAccessToken()
if err != nil {
return nil, "", err
return nil, err
}
var response []byte
jsonData, err := json.Marshal(request)
if err != nil {
return nil, "", err
return nil, err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", fetchBatchExternalContactUserDetailURL, accessToken), string(jsonData))
if err != nil {
return nil, "", err
return nil, err
}
var result ExternalUserDetailListResponse
err = util.DecodeWithError(response, &result, "BatchGetExternalUserDetails")
return result.ExternalContactList, result.NextCursor, err
return result.ExternalContactList, err
}
// UpdateUserRemarkRequest 修改客户备注信息请求体

View File

@@ -19,7 +19,6 @@ type (
RoomBaseID int `json:"room_base_id"` //非必填 自动建群的群起始序号当auto_create_room为1时有效
ChatIDList []string `json:"chat_id_list"` //必填 使用该配置的客户群ID列表支持5个。见客户群ID获取方法
State string `json:"state"` //非必填 企业自定义的state参数用于区分不同的入群渠道。不超过30个UTF-8字符
MarkSource bool `json:"mark_source"`
}
// AddJoinWayResponse 添加群配置返回值
@@ -66,7 +65,6 @@ type (
ChatIDList []string `json:"chat_id_list"`
QrCode string `json:"qr_code"`
State string `json:"state"`
MarkSource bool `json:"mark_source"`
}
//GetJoinWayResponse 获取群配置的返回值
GetJoinWayResponse struct {
@@ -105,7 +103,6 @@ type UpdateJoinWayRequest struct {
RoomBaseID int `json:"room_base_id"` //非必填 自动建群的群起始序号当auto_create_room为1时有效
ChatIDList []string `json:"chat_id_list"` //必填 使用该配置的客户群ID列表支持5个。见客户群ID获取方法
State string `json:"state"` //非必填 企业自定义的state参数用于区分不同的入群渠道。不超过30个UTF-8字符
MarkSource bool `json:"mark_source"`
}
// UpdateJoinWay 更新客户群进群方式配置

View File

@@ -1,75 +0,0 @@
package jsapi
import (
"fmt"
"github.com/silenceper/wechat/v2/credential"
"github.com/silenceper/wechat/v2/util"
"github.com/silenceper/wechat/v2/work/context"
)
// Js struct
type Js struct {
*context.Context
jsTicket *credential.WorkJsTicket
}
// NewJs init
func NewJs(context *context.Context) *Js {
js := new(Js)
js.Context = context
js.jsTicket = credential.NewWorkJsTicket(
context.Config.CorpID,
context.Config.AgentID,
credential.CacheKeyWorkPrefix,
context.Cache,
)
return js
}
// Config 返回给用户使用的配置
type Config struct {
Timestamp int64 `json:"timestamp"`
NonceStr string `json:"nonce_str"`
Signature string `json:"signature"`
}
// GetConfig 获取企业微信JS配置 https://developer.work.weixin.qq.com/document/path/90514
func (js *Js) GetConfig(uri string) (config *Config, err error) {
config = new(Config)
var accessToken string
accessToken, err = js.GetAccessToken()
if err != nil {
return
}
var ticketStr string
ticketStr, err = js.jsTicket.GetTicket(accessToken, credential.TicketTypeCorpJs)
if err != nil {
return
}
config.NonceStr = util.RandomStr(16)
config.Timestamp = util.GetCurrTS()
str := fmt.Sprintf("jsapi_ticket=%s&noncestr=%s&timestamp=%d&url=%s", ticketStr, config.NonceStr, config.Timestamp, uri)
config.Signature = util.Signature(str)
return
}
// GetAgentConfig 获取企业微信应用JS配置 https://developer.work.weixin.qq.com/document/path/94313
func (js *Js) GetAgentConfig(uri string) (config *Config, err error) {
config = new(Config)
var accessToken string
accessToken, err = js.GetAccessToken()
if err != nil {
return
}
var ticketStr string
ticketStr, err = js.jsTicket.GetTicket(accessToken, credential.TicketTypeAgentJs)
if err != nil {
return
}
config.NonceStr = util.RandomStr(16)
config.Timestamp = util.GetCurrTS()
str := fmt.Sprintf("jsapi_ticket=%s&noncestr=%s&timestamp=%d&url=%s", ticketStr, config.NonceStr, config.Timestamp, uri)
config.Signature = util.Signature(str)
return
}

View File

@@ -33,7 +33,6 @@ type AccountAddSchema struct {
}
// AccountAdd 添加客服账号
// see https://developer.work.weixin.qq.com/document/path/94662
func (r *Client) AccountAdd(options AccountAddOptions) (info AccountAddSchema, err error) {
var (
accessToken string
@@ -60,7 +59,6 @@ type AccountDelOptions struct {
}
// AccountDel 删除客服账号
// see https://developer.work.weixin.qq.com/document/path/94663
func (r *Client) AccountDel(options AccountDelOptions) (info util.CommonError, err error) {
var (
accessToken string
@@ -88,8 +86,7 @@ type AccountUpdateOptions struct {
MediaID string `json:"media_id"` // 客服头像临时素材。可以调用上传临时素材接口获取, 不多于128个字节
}
// AccountUpdate 修客服账号
// see https://developer.work.weixin.qq.com/document/path/94664
// AccountUpdate 修客服账号
func (r *Client) AccountUpdate(options AccountUpdateOptions) (info util.CommonError, err error) {
var (
accessToken string
@@ -112,10 +109,9 @@ func (r *Client) AccountUpdate(options AccountUpdateOptions) (info util.CommonEr
// AccountInfoSchema 客服详情
type AccountInfoSchema struct {
OpenKFID string `json:"open_kfid"` // 客服帐号ID
Name string `json:"name"` // 客服帐号名称
Avatar string `json:"avatar"` // 客服头像URL
ManagePrivilege bool `json:"manage_privilege"` // 当前调用接口的应用身份,是否有该客服账号的管理权限(编辑客服账号信息、分配会话和收发消息)
OpenKFID string `json:"open_kfid"` // 客服帐号ID
Name string `json:"name"` // 客服帐号名称
Avatar string `json:"avatar"` // 客服头像URL
}
// AccountListSchema 获取客服账号列表响应内容
@@ -145,31 +141,6 @@ func (r *Client) AccountList() (info AccountListSchema, err error) {
return info, nil
}
// AccountPagingRequest 分页获取客服账号列表请求
type AccountPagingRequest struct {
Offset int `json:"offset"`
Limit int `json:"limit"`
}
// AccountPaging 分页获取客服账号列表
// see https://developer.work.weixin.qq.com/document/path/94661
func (r *Client) AccountPaging(req *AccountPagingRequest) (*AccountListSchema, error) {
var (
accessToken string
err error
)
if accessToken, err = r.ctx.GetAccessToken(); err != nil {
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(accountListAddr, accessToken), req); err != nil {
return nil, err
}
result := &AccountListSchema{}
err = util.DecodeWithError(response, result, "AccountPaging")
return result, err
}
// AddContactWayOptions 获取客服账号链接
// 1.若scene非空返回的客服链接开发者可拼接scene_param=SCENE_PARAM参数使用用户进入会话事件会将SCENE_PARAM原样返回。其中SCENE_PARAM需要urlencode且长度不能超过128字节。
// 如 https://work.weixin.qq.com/kf/kfcbf8f8d07ac7215f?enc_scene=ENCGFSDF567DF&scene_param=a%3D1%26b%3D2
@@ -187,7 +158,6 @@ type AddContactWaySchema struct {
}
// AddContactWay 获取客服账号链接
// see https://developer.work.weixin.qq.com/document/path/94665
func (r *Client) AddContactWay(options AddContactWayOptions) (info AddContactWaySchema, err error) {
var (
accessToken string

View File

@@ -8,10 +8,10 @@ import (
// SignatureOptions 微信服务器验证参数
type SignatureOptions struct {
Signature string `form:"msg_signature" json:"msg_signature"`
TimeStamp string `form:"timestamp" json:"timestamp"`
Nonce string `form:"nonce" json:"nonce"`
EchoStr string `form:"echostr" json:"echostr"`
Signature string `form:"msg_signature"`
TimeStamp string `form:"timestamp"`
Nonce string `form:"nonce"`
EchoStr string `form:"echostr"`
}
// VerifyURL 验证请求参数是否合法并返回解密后的消息内容

View File

@@ -24,7 +24,7 @@ func NewClient(cfg *config.Config) (client *Client, err error) {
}
// 初始化 AccessToken Handle
defaultAkHandle := credential.NewWorkAccessToken(cfg.CorpID, cfg.CorpSecret, cfg.AgentID, credential.CacheKeyWorkPrefix, cfg.Cache)
defaultAkHandle := credential.NewWorkAccessToken(cfg.CorpID, cfg.CorpSecret, credential.CacheKeyWorkPrefix, cfg.Cache)
ctx := &context.Context{
Config: cfg,
AccessTokenHandle: defaultAkHandle,

View File

@@ -18,23 +18,20 @@ const (
// ReceptionistOptions 添加接待人员请求参数
type ReceptionistOptions struct {
OpenKFID string `json:"open_kfid"` // 客服帐号ID
UserIDList []string `json:"userid_list"` // 接待人员userid列表。第三方应用填密文userid即open_userid 可填充个数1 ~ 100。超过100个需分批调用。
DepartmentIDList []int `json:"department_id_list"` // 接待人员部门id列表 可填充个数0 ~ 100。超过100个需分批调用。
OpenKFID string `json:"open_kfid"` // 客服帐号ID
UserIDList []string `json:"userid_list"` // 接待人员userid列表。第三方应用填密文userid即open_userid 可填充个数1 ~ 100。超过100个需分批调用。
}
// ReceptionistSchema 添加接待人员响应内容
type ReceptionistSchema struct {
util.CommonError
ResultList []struct {
UserID string `json:"userid"`
DepartmentID int `json:"department_id"`
UserID string `json:"userid"`
util.CommonError
} `json:"result_list"`
}
// ReceptionistAdd 添加接待人员
// @see https://developer.work.weixin.qq.com/document/path/94646
func (r *Client) ReceptionistAdd(options ReceptionistOptions) (info ReceptionistSchema, err error) {
var (
accessToken string
@@ -52,11 +49,10 @@ func (r *Client) ReceptionistAdd(options ReceptionistOptions) (info Receptionist
if info.ErrCode != 0 {
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
}
return
return info, nil
}
// ReceptionistDel 删除接待人员
// @see https://developer.work.weixin.qq.com/document/path/94647
func (r *Client) ReceptionistDel(options ReceptionistOptions) (info ReceptionistSchema, err error) {
var (
accessToken string
@@ -76,22 +72,19 @@ func (r *Client) ReceptionistDel(options ReceptionistOptions) (info Receptionist
if info.ErrCode != 0 {
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
}
return
return info, nil
}
// ReceptionistListSchema 获取接待人员列表响应内容
type ReceptionistListSchema struct {
util.CommonError
ReceptionistList []struct {
UserID string `json:"userid"` // 接待人员的userid。第三方应用获取到的为密文userid即open_userid
Status int `json:"status"` // 接待人员的接待状态。0:接待中,1:停止接待。第三方应用需具有“管理帐号、分配会话和收发消息”权限才可获取
DepartmentID int `json:"department_id"` // 接待人员部门的id
StopType int `json:"stop_type"` // 接待人员的接待状态为「停止接待」的子类型。0:停止接待,1:暂时挂起
UserID string `json:"userid"` // 接待人员的userid。第三方应用获取到的为密文userid即open_userid
Status int `json:"status"` // 接待人员的接待状态。0:接待中,1:停止接待。第三方应用需具有“管理帐号、分配会话和收发消息”权限才可获取
} `json:"servicer_list"`
}
// ReceptionistList 获取接待人员列表
// @see https://developer.work.weixin.qq.com/document/path/94645
func (r *Client) ReceptionistList(kfID string) (info ReceptionistListSchema, err error) {
var (
accessToken string
@@ -111,5 +104,5 @@ func (r *Client) ReceptionistList(kfID string) (info ReceptionistListSchema, err
if info.ErrCode != 0 {
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
}
return
return info, nil
}

View File

@@ -14,8 +14,6 @@ const (
uploadTempFile = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s"
// uploadAttachment 上传附件资源
uploadAttachment = "https://qyapi.weixin.qq.com/cgi-bin/media/upload_attachment?access_token=%s&media_type=%s&attachment_type=%d"
// getTempFile 获取临时素材
getTempFile = "https://qyapi.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s"
)
// UploadImgResponse 上传图片响应
@@ -59,30 +57,6 @@ func (r *Client) UploadImg(filename string) (*UploadImgResponse, error) {
return result, err
}
// UploadImgFromReader 从 io.Reader 上传图片
// @see https://developer.work.weixin.qq.com/document/path/90256
func (r *Client) UploadImgFromReader(filename string, reader io.Reader) (*UploadImgResponse, error) {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
var byteData []byte
byteData, err = io.ReadAll(reader)
if err != nil {
return nil, err
}
var response []byte
if response, err = util.PostFileByStream("media", filename, fmt.Sprintf(uploadImgURL, accessToken), byteData); err != nil {
return nil, err
}
result := &UploadImgResponse{}
err = util.DecodeWithError(response, result, "UploadImg")
return result, err
}
// UploadTempFile 上传临时素材
// @see https://developer.work.weixin.qq.com/document/path/90253
// @mediaType 媒体文件类型分别有图片image、语音voice、视频video普通文件file
@@ -174,23 +148,3 @@ func (r *Client) UploadAttachmentFromReader(filename, mediaType string, reader i
err = util.DecodeWithError(response, result, "UploadAttachment")
return result, err
}
// GetTempFile 获取临时素材
// @see https://developer.work.weixin.qq.com/document/path/90254
func (r *Client) GetTempFile(mediaID string) ([]byte, error) {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
url := fmt.Sprintf(getTempFile, accessToken, mediaID)
response, err := util.HTTPGet(url)
if err != nil {
return nil, err
}
// 检查响应是否为错误信息,如果不是错误响应,则返回原始数据
return util.HandleFileResponse(response, "GetTempFile")
}

View File

@@ -9,7 +9,6 @@ import (
"github.com/silenceper/wechat/v2/work/context"
"github.com/silenceper/wechat/v2/work/externalcontact"
"github.com/silenceper/wechat/v2/work/invoice"
"github.com/silenceper/wechat/v2/work/jsapi"
"github.com/silenceper/wechat/v2/work/kf"
"github.com/silenceper/wechat/v2/work/material"
"github.com/silenceper/wechat/v2/work/message"
@@ -25,7 +24,7 @@ type Work struct {
// NewWork init work
func NewWork(cfg *config.Config) *Work {
defaultAkHandle := credential.NewWorkAccessToken(cfg.CorpID, cfg.CorpSecret, cfg.AgentID, credential.CacheKeyWorkPrefix, cfg.Cache)
defaultAkHandle := credential.NewWorkAccessToken(cfg.CorpID, cfg.CorpSecret, credential.CacheKeyWorkPrefix, cfg.Cache)
ctx := &context.Context{
Config: cfg,
AccessTokenHandle: defaultAkHandle,
@@ -53,11 +52,6 @@ func (wk *Work) GetKF() (*kf.Client, error) {
return kf.NewClient(wk.ctx.Config)
}
// JsSdk get JsSdk
func (wk *Work) JsSdk() *jsapi.Js {
return jsapi.NewJs(wk.ctx)
}
// GetExternalContact get external_contact
func (wk *Work) GetExternalContact() *externalcontact.Client {
return externalcontact.NewClient(wk.ctx)