parent
b77d14b664
commit
65da76425a
@ -1,40 +0,0 @@
|
|||||||
name: coverage
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ v3 ]
|
|
||||||
paths-ignore:
|
|
||||||
- '**.md'
|
|
||||||
pull_request:
|
|
||||||
branches: [ v3 ]
|
|
||||||
paths-ignore:
|
|
||||||
- '**.md'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
name: Test with Coverage
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: 1.17
|
|
||||||
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
go mod download
|
|
||||||
|
|
||||||
- name: Run Unit tests
|
|
||||||
run: |
|
|
||||||
go test -race -covermode atomic -coverprofile=covprofile ./...
|
|
||||||
|
|
||||||
- name: Install goveralls
|
|
||||||
run: go install github.com/mattn/goveralls@latest
|
|
||||||
|
|
||||||
- name: Send coverage
|
|
||||||
env:
|
|
||||||
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: goveralls -coverprofile=covprofile -service=github
|
|
@ -1,31 +0,0 @@
|
|||||||
name: Go
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ v3 ]
|
|
||||||
paths-ignore:
|
|
||||||
- '**.md'
|
|
||||||
pull_request:
|
|
||||||
branches: [ v3 ]
|
|
||||||
paths-ignore:
|
|
||||||
- '**.md'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: 1.17
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: go test -v ./...
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: go test ./... -race -coverprofile=coverage.txt -covermode=atomic
|
|
||||||
|
|
||||||
- name: Coverage
|
|
||||||
run: bash <(curl -s https://codecov.io/bash)
|
|
@ -1 +0,0 @@
|
|||||||
Subproject commit 7d3f9e33ee862f8acd65a4cb8ecffb56c4b860a2
|
|
@ -0,0 +1,125 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
BLOCK_SIZE = 32 // PKCS#7
|
||||||
|
BLOCK_MASK = BLOCK_SIZE - 1 // BLOCK_SIZE 为 2^n 时, 可以用 mask 获取针对 BLOCK_SIZE 的余数
|
||||||
|
)
|
||||||
|
|
||||||
|
// 把整数 n 格式化成 4 字节的网络字节序
|
||||||
|
func encodeNetworkByteOrder(b []byte, n uint32) {
|
||||||
|
b[0] = byte(n >> 24)
|
||||||
|
b[1] = byte(n >> 16)
|
||||||
|
b[2] = byte(n >> 8)
|
||||||
|
b[3] = byte(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 4 字节的网络字节序里解析出整数
|
||||||
|
func decodeNetworkByteOrder(b []byte) (n uint32) {
|
||||||
|
return uint32(b[0])<<24 |
|
||||||
|
uint32(b[1])<<16 |
|
||||||
|
uint32(b[2])<<8 |
|
||||||
|
uint32(b[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
// AESEncryptMsg ciphertext = AES_Encrypt[random(16B) + msg_len(4B) + rawXMLMsg + appId]
|
||||||
|
func AESEncryptMsg(random, rawXMLMsg []byte, appId string, aesKey []byte) (ciphertext []byte) {
|
||||||
|
appIdOffset := 20 + len(rawXMLMsg)
|
||||||
|
contentLen := appIdOffset + len(appId)
|
||||||
|
amountToPad := BLOCK_SIZE - contentLen&BLOCK_MASK
|
||||||
|
plaintextLen := contentLen + amountToPad
|
||||||
|
|
||||||
|
plaintext := make([]byte, plaintextLen)
|
||||||
|
|
||||||
|
// 拼接
|
||||||
|
copy(plaintext[:16], random)
|
||||||
|
encodeNetworkByteOrder(plaintext[16:20], uint32(len(rawXMLMsg)))
|
||||||
|
copy(plaintext[20:], rawXMLMsg)
|
||||||
|
copy(plaintext[appIdOffset:], appId)
|
||||||
|
|
||||||
|
// PKCS#7 补位
|
||||||
|
for i := contentLen; i < plaintextLen; i++ {
|
||||||
|
plaintext[i] = byte(amountToPad)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加密
|
||||||
|
block, err := aes.NewCipher(aesKey)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
mode := cipher.NewCBCEncrypter(block, aesKey[:16])
|
||||||
|
mode.CryptBlocks(plaintext, plaintext)
|
||||||
|
|
||||||
|
ciphertext = plaintext
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// AESDecryptMsg c解密
|
||||||
|
func AESDecryptMsg(decryptStr, aesKey string) (string, error) {
|
||||||
|
cipherText, err := base64.StdEncoding.DecodeString(decryptStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解密
|
||||||
|
block, err := aes.NewCipher([]byte(aesKey))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
blockMode := cipher.NewCBCDecrypter(block, []byte(aesKey))
|
||||||
|
decrypted := make([]byte, len(cipherText))
|
||||||
|
blockMode.CryptBlocks(decrypted, cipherText)
|
||||||
|
|
||||||
|
decrypted = pkcs5UnPadding(decrypted)
|
||||||
|
return string(decrypted), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func pkcs5UnPadding(decrypted []byte) []byte {
|
||||||
|
length := len(decrypted)
|
||||||
|
unPadding := int(decrypted[length-1])
|
||||||
|
return decrypted[:(length - unPadding)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func AESDecryptData(cipherText, aesKey, iv []byte) (rawData []byte, err error) {
|
||||||
|
if len(cipherText) < BLOCK_SIZE {
|
||||||
|
err = fmt.Errorf("the length of ciphertext too short: %d", len(cipherText))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
plaintext := make([]byte, len(cipherText)) // len(plaintext) >= BLOCK_SIZE
|
||||||
|
|
||||||
|
// 解密
|
||||||
|
block, err := aes.NewCipher(aesKey)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
mode := cipher.NewCBCDecrypter(block, iv)
|
||||||
|
mode.CryptBlocks(plaintext, cipherText)
|
||||||
|
|
||||||
|
// PKCS#7 去除补位
|
||||||
|
amountToPad := int(plaintext[len(plaintext)-1])
|
||||||
|
if amountToPad < 1 || amountToPad > BLOCK_SIZE {
|
||||||
|
err = fmt.Errorf("the amount to pad is incorrect: %d", amountToPad)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
plaintext = plaintext[:len(plaintext)-amountToPad]
|
||||||
|
|
||||||
|
// 反拼接
|
||||||
|
// len(plaintext) == 16+4+len(rawXMLMsg)+len(appId)
|
||||||
|
if len(plaintext) <= 20 {
|
||||||
|
err = fmt.Errorf("plaintext too short, the length is %d", len(plaintext))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rawData = plaintext
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"dtapps/dta/library/utils/gomongo"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"gitee.com/dtapps/go-library/utils/gohttp"
|
||||||
|
"gitee.com/dtapps/go-library/utils/gotime"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// App 微信公众号服务
|
||||||
|
type App struct {
|
||||||
|
componentAccessToken string // 第三方平台 access_token
|
||||||
|
componentVerifyTicket string // 微信后台推送的 ticket
|
||||||
|
preAuthCode string // 预授权码
|
||||||
|
|
||||||
|
authorizerAccessToken string // 接口调用令牌
|
||||||
|
authorizerRefreshToken string // 刷新令牌
|
||||||
|
AuthorizerAppid string // 授权方 appid
|
||||||
|
|
||||||
|
ComponentAppId string // 第三方平台 appid
|
||||||
|
ComponentAppSecret string // 第三方平台 app_secret
|
||||||
|
MessageToken string
|
||||||
|
MessageKey string
|
||||||
|
|
||||||
|
Mongo gomongo.App // 非关系数据库服务
|
||||||
|
Db *gorm.DB // 关系数据库服务
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) request(url string, params map[string]interface{}, method string) (resp []byte, err error) {
|
||||||
|
switch method {
|
||||||
|
case http.MethodGet:
|
||||||
|
get, err := gohttp.Get(url, params)
|
||||||
|
// 日志
|
||||||
|
go app.mongoLog(url, params, method, get)
|
||||||
|
return get.Body, err
|
||||||
|
case http.MethodPost:
|
||||||
|
// 请求参数
|
||||||
|
paramsStr, err := json.Marshal(params)
|
||||||
|
postJson, err := gohttp.PostJson(url, paramsStr)
|
||||||
|
// 日志
|
||||||
|
go app.mongoLog(url, params, method, postJson)
|
||||||
|
return postJson.Body, err
|
||||||
|
default:
|
||||||
|
return nil, errors.New("请求类型不支持")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuthorizerAccessToken 获取授权方令牌
|
||||||
|
func (app *App) GetAuthorizerAccessToken() string {
|
||||||
|
if app.Db == nil {
|
||||||
|
return app.authorizerAccessToken
|
||||||
|
}
|
||||||
|
var result AuthorizerAccessToken
|
||||||
|
app.Db.Where("component_app_id = ?", app.ComponentAppId).Where("authorizer_app_id = ?", app.AuthorizerAppid).Where("expire_time >= ?", gotime.Current().Format()).Last(&result)
|
||||||
|
return result.AuthorizerAccessToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuthorizerRefreshToken 获取刷新令牌
|
||||||
|
func (app *App) GetAuthorizerRefreshToken() string {
|
||||||
|
if app.Db == nil {
|
||||||
|
return app.authorizerRefreshToken
|
||||||
|
}
|
||||||
|
var result AuthorizerAccessToken
|
||||||
|
app.Db.Where("component_app_id = ?", app.ComponentAppId).Where("authorizer_app_id = ?", app.AuthorizerAppid).Last(&result)
|
||||||
|
return result.AuthorizerRefreshToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPreAuthCode 获取预授权码
|
||||||
|
func (app *App) GetPreAuthCode() string {
|
||||||
|
if app.Db == nil {
|
||||||
|
return app.preAuthCode
|
||||||
|
}
|
||||||
|
var result PreAuthCode
|
||||||
|
app.Db.Where("app_id = ?", app.ComponentAppId).Where("expire_time >= ?", gotime.Current().Format()).Last(&result)
|
||||||
|
return result.PreAuthCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetComponentAccessToken 获取 access_token
|
||||||
|
func (app *App) GetComponentAccessToken() string {
|
||||||
|
if app.Db == nil {
|
||||||
|
return app.componentAccessToken
|
||||||
|
}
|
||||||
|
var result ComponentAccessToken
|
||||||
|
app.Db.Where("app_id = ?", app.ComponentAppId).Where("expire_time >= ?", gotime.Current().Format()).Last(&result)
|
||||||
|
return result.ComponentAccessToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetComponentVerifyTicket 获取 Ticket
|
||||||
|
func (app *App) GetComponentVerifyTicket() string {
|
||||||
|
if app.Db == nil {
|
||||||
|
return app.componentVerifyTicket
|
||||||
|
}
|
||||||
|
var result ComponentVerifyTicket
|
||||||
|
app.Db.Where("app_id = ?", app.ComponentAppId).Where("expire_time >= ?", gotime.Current().Format()).Last(&result)
|
||||||
|
return result.ComponentVerifyTicket
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CgiBinComponentApiAuthorizerTokenResponse struct {
|
||||||
|
AuthorizerAccessToken string `json:"authorizer_access_token"`
|
||||||
|
ExpiresIn int64 `json:"expires_in"`
|
||||||
|
AuthorizerRefreshToken string `json:"authorizer_refresh_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CgiBinComponentApiAuthorizerTokenResult struct {
|
||||||
|
Result CgiBinComponentApiAuthorizerTokenResponse // 结果
|
||||||
|
Body []byte // 内容
|
||||||
|
Err error // 错误
|
||||||
|
authorizerAppid string // 授权方 appid
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCgiBinComponentApiAuthorizerTokenResult(result CgiBinComponentApiAuthorizerTokenResponse, body []byte, err error, authorizerAppid string) *CgiBinComponentApiAuthorizerTokenResult {
|
||||||
|
return &CgiBinComponentApiAuthorizerTokenResult{Result: result, Body: body, Err: err, authorizerAppid: authorizerAppid}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CgiBinComponentApiAuthorizerToken 获取/刷新接口调用令牌
|
||||||
|
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/ThirdParty/token/api_authorizer_token.html
|
||||||
|
func (app *App) CgiBinComponentApiAuthorizerToken() *CgiBinComponentApiAuthorizerTokenResult {
|
||||||
|
app.componentAccessToken = app.GetComponentAccessToken()
|
||||||
|
// 参数
|
||||||
|
param := NewParams()
|
||||||
|
param["component_appid"] = app.ComponentAppId // 第三方平台 appid
|
||||||
|
param["authorizer_appid"] = app.AuthorizerAppid // 授权方 appid
|
||||||
|
param["authorizer_refresh_token"] = app.GetAuthorizerRefreshToken() // 授权码, 会在授权成功时返回给第三方平台
|
||||||
|
params := app.NewParamsWith(param)
|
||||||
|
// 请求
|
||||||
|
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/component/api_authorizer_token?component_access_token=%v", app.componentAccessToken), params, http.MethodPost)
|
||||||
|
// 定义
|
||||||
|
var response CgiBinComponentApiAuthorizerTokenResponse
|
||||||
|
err = json.Unmarshal(body, &response)
|
||||||
|
return NewCgiBinComponentApiAuthorizerTokenResult(response, body, err, app.AuthorizerAppid)
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CgiBinComponentApiComponentTokenResponse struct {
|
||||||
|
ComponentAccessToken string `json:"component_access_token"` // 第三方平台 access_token
|
||||||
|
ExpiresIn int64 `json:"expires_in"` // 有效期,单位:秒
|
||||||
|
}
|
||||||
|
|
||||||
|
type CgiBinComponentApiComponentTokenResult struct {
|
||||||
|
Result CgiBinComponentApiComponentTokenResponse // 结果
|
||||||
|
Body []byte // 内容
|
||||||
|
Err error // 错误
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCgiBinComponentApiComponentTokenResult(result CgiBinComponentApiComponentTokenResponse, body []byte, err error) *CgiBinComponentApiComponentTokenResult {
|
||||||
|
return &CgiBinComponentApiComponentTokenResult{Result: result, Body: body, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CgiBinComponentApiComponentToken 令牌
|
||||||
|
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/ThirdParty/token/component_access_token.html
|
||||||
|
func (app *App) CgiBinComponentApiComponentToken() *CgiBinComponentApiComponentTokenResult {
|
||||||
|
app.componentVerifyTicket = app.GetComponentVerifyTicket()
|
||||||
|
// 参数
|
||||||
|
param := NewParams()
|
||||||
|
param["component_appid"] = app.ComponentAppId // 第三方平台 appid
|
||||||
|
param["component_appsecret"] = app.ComponentAppSecret // 第三方平台 appsecret
|
||||||
|
param["component_verify_ticket"] = app.componentVerifyTicket // 微信后台推送的 ticket
|
||||||
|
params := app.NewParamsWith(param)
|
||||||
|
// 请求
|
||||||
|
body, err := app.request("https://api.weixin.qq.com/cgi-bin/component/api_component_token", params, http.MethodPost)
|
||||||
|
// 定义
|
||||||
|
var response CgiBinComponentApiComponentTokenResponse
|
||||||
|
err = json.Unmarshal(body, &response)
|
||||||
|
return NewCgiBinComponentApiComponentTokenResult(response, body, err)
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CgiBinComponentApiCreatePreAuthCodenResponse struct {
|
||||||
|
PreAuthCode string `json:"pre_auth_code"` // 预授权码
|
||||||
|
ExpiresIn int64 `json:"expires_in"` // 有效期,单位:秒
|
||||||
|
}
|
||||||
|
|
||||||
|
type CgiBinComponentApiCreatePreAuthCodenResult struct {
|
||||||
|
Result CgiBinComponentApiCreatePreAuthCodenResponse // 结果
|
||||||
|
Body []byte // 内容
|
||||||
|
Err error // 错误
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCgiBinComponentApiCreatePreAuthCodenResult(result CgiBinComponentApiCreatePreAuthCodenResponse, body []byte, err error) *CgiBinComponentApiCreatePreAuthCodenResult {
|
||||||
|
return &CgiBinComponentApiCreatePreAuthCodenResult{Result: result, Body: body, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CgiBinComponentApiCreatePreAuthCoden 预授权码
|
||||||
|
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/ThirdParty/token/pre_auth_code.html
|
||||||
|
func (app *App) CgiBinComponentApiCreatePreAuthCoden() *CgiBinComponentApiCreatePreAuthCodenResult {
|
||||||
|
app.componentAccessToken = app.GetComponentAccessToken()
|
||||||
|
// 参数
|
||||||
|
param := NewParams()
|
||||||
|
param["component_appid"] = app.ComponentAppId // 第三方平台 appid
|
||||||
|
params := app.NewParamsWith(param)
|
||||||
|
// 请求
|
||||||
|
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=%v", app.componentAccessToken), params, http.MethodPost)
|
||||||
|
// 定义
|
||||||
|
var response CgiBinComponentApiCreatePreAuthCodenResponse
|
||||||
|
err = json.Unmarshal(body, &response)
|
||||||
|
return NewCgiBinComponentApiCreatePreAuthCodenResult(response, body, err)
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetCallBackIpResponse struct {
|
||||||
|
IpList []string `json:"ip_list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetCallBackIpResult struct {
|
||||||
|
Result GetCallBackIpResponse // 结果
|
||||||
|
Body []byte // 内容
|
||||||
|
Err error // 错误
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGetCallBackIpResult(result GetCallBackIpResponse, body []byte, err error) *GetCallBackIpResult {
|
||||||
|
return &GetCallBackIpResult{Result: result, Body: body, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CgiBinGetApiDomainIp 获取微信服务器IP地址
|
||||||
|
// https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_the_WeChat_server_IP_address.html
|
||||||
|
func (app *App) CgiBinGetApiDomainIp(componentAccessToken string) *GetCallBackIpResult {
|
||||||
|
// 请求
|
||||||
|
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/get_api_domain_ip?access_token=%s", componentAccessToken), map[string]interface{}{}, http.MethodGet)
|
||||||
|
// 定义
|
||||||
|
var response GetCallBackIpResponse
|
||||||
|
err = json.Unmarshal(body, &response)
|
||||||
|
return NewGetCallBackIpResult(response, body, err)
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitee.com/dtapps/go-library/utils/gotime"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetComponentAccessTokenMonitor 获取令牌和监控
|
||||||
|
func (app *App) GetComponentAccessTokenMonitor() string {
|
||||||
|
// 查询
|
||||||
|
componentAccessToken := app.GetComponentAccessToken()
|
||||||
|
// 判断
|
||||||
|
result := app.CgiBinGetApiDomainIp(componentAccessToken)
|
||||||
|
if len(result.Result.IpList) > 0 {
|
||||||
|
return componentAccessToken
|
||||||
|
}
|
||||||
|
// 重新获取
|
||||||
|
return app.SetComponentAccessToken(app.CgiBinComponentApiComponentToken())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetComponentAccessToken 设置令牌
|
||||||
|
func (app *App) SetComponentAccessToken(info *CgiBinComponentApiComponentTokenResult) string {
|
||||||
|
if app.Db == nil || info.Result.ComponentAccessToken == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
app.Db.Create(&ComponentAccessToken{
|
||||||
|
AppId: app.ComponentAppId,
|
||||||
|
ComponentAccessToken: info.Result.ComponentAccessToken,
|
||||||
|
ExpiresIn: info.Result.ExpiresIn,
|
||||||
|
ExpireTime: gotime.Current().AfterSeconds(7200).Time,
|
||||||
|
})
|
||||||
|
return info.Result.ComponentAccessToken
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComponentAccessToken struct {
|
||||||
|
gorm.Model
|
||||||
|
AppId string `json:"app_id"` // 第三方平台 appid
|
||||||
|
ComponentAccessToken string `json:"component_access_token"` // 第三方平台 access_token
|
||||||
|
ExpiresIn int64 `json:"expires_in"` // 有效期,单位:秒
|
||||||
|
ExpireTime time.Time `json:"expire_time"` // 过期时间
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ComponentAccessToken) TableName() string {
|
||||||
|
return "component_access_token"
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"gitee.com/dtapps/go-library/utils/gohttp"
|
||||||
|
"gitee.com/dtapps/go-library/utils/gotime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 日志
|
||||||
|
type mongoZap struct {
|
||||||
|
Url string `json:"url" bson:"url"`
|
||||||
|
Params interface{} `json:"params" bson:"params"`
|
||||||
|
Method string `json:"method" bson:"method"`
|
||||||
|
Header interface{} `json:"header" bson:"header"`
|
||||||
|
Status string `json:"status" bson:"status"`
|
||||||
|
StatusCode int `json:"status_code" bson:"status_code"`
|
||||||
|
Body interface{} `json:"body" bson:"body"`
|
||||||
|
ContentLength int64 `json:"content_length" bson:"content_length"`
|
||||||
|
CreateTime string `json:"create_time" bson:"create_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mongoZap) Database() string {
|
||||||
|
return "zap_logs"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mongoZap) TableName() string {
|
||||||
|
return "wechatopen_" + gotime.Current().SetFormat("200601")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) mongoLog(url string, params map[string]interface{}, method string, request gohttp.Response) {
|
||||||
|
if app.Mongo.Db == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var body map[string]interface{}
|
||||||
|
_ = json.Unmarshal(request.Body, &body)
|
||||||
|
app.Mongo.Model(&mongoZap{}).InsertOne(mongoZap{
|
||||||
|
Url: url,
|
||||||
|
Params: params,
|
||||||
|
Method: method,
|
||||||
|
Header: request.Header,
|
||||||
|
Status: request.Status,
|
||||||
|
StatusCode: request.StatusCode,
|
||||||
|
Body: body,
|
||||||
|
ContentLength: request.ContentLength,
|
||||||
|
CreateTime: gotime.Current().Format(),
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
// Params 请求参数
|
||||||
|
type Params map[string]interface{}
|
||||||
|
|
||||||
|
func NewParams() Params {
|
||||||
|
p := make(Params)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) NewParamsWith(params ...Params) Params {
|
||||||
|
p := make(Params)
|
||||||
|
for _, v := range params {
|
||||||
|
p.SetParams(v)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Params) Set(key string, value interface{}) {
|
||||||
|
p[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Params) SetParams(params Params) {
|
||||||
|
for key, value := range params {
|
||||||
|
p[key] = value
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitee.com/dtapps/go-library/utils/gotime"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetPreAuthCodeMonitor 获取预授权码和监控
|
||||||
|
func (app *App) GetPreAuthCodeMonitor() string {
|
||||||
|
// 查询
|
||||||
|
preAuthCode := app.GetPreAuthCode()
|
||||||
|
if preAuthCode != "" {
|
||||||
|
return preAuthCode
|
||||||
|
}
|
||||||
|
// 重新获取
|
||||||
|
return app.SetPreAuthCode(app.CgiBinComponentApiCreatePreAuthCoden())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPreAuthCode 设置预授权码和自动获取
|
||||||
|
func (app *App) SetPreAuthCode(info *CgiBinComponentApiCreatePreAuthCodenResult) string {
|
||||||
|
if app.Db == nil || info.Result.PreAuthCode == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
app.Db.Create(&PreAuthCode{
|
||||||
|
AppId: app.ComponentAppId,
|
||||||
|
PreAuthCode: info.Result.PreAuthCode,
|
||||||
|
ExpiresIn: info.Result.ExpiresIn,
|
||||||
|
ExpireTime: gotime.Current().AfterSeconds(1700).Time,
|
||||||
|
})
|
||||||
|
return info.Result.PreAuthCode
|
||||||
|
}
|
||||||
|
|
||||||
|
type PreAuthCode struct {
|
||||||
|
gorm.Model
|
||||||
|
AppId string `json:"app_id"` // 第三方平台 appid
|
||||||
|
PreAuthCode string `json:"pre_auth_code"` // 预授权码
|
||||||
|
ExpiresIn int64 `json:"expires_in"` // 有效期,单位:秒
|
||||||
|
ExpireTime time.Time `json:"expire_time"` // 过期时间
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *PreAuthCode) TableName() string {
|
||||||
|
return "pre_auth_code"
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreAuthCodeDelete 删除过期或使用过的预授权码
|
||||||
|
func (app *App) PreAuthCodeDelete(id uint) int64 {
|
||||||
|
return app.Db.Where("id = ?", id).Delete(&PreAuthCode{}).RowsAffected
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServeHttpAuthorizerAppid 授权跳转
|
||||||
|
func (app *App) ServeHttpAuthorizerAppid(r *http.Request) (resp CgiBinComponentApiQueryAuthResponse, agentUserId int64, pacId uint, err error) {
|
||||||
|
var (
|
||||||
|
query = r.URL.Query()
|
||||||
|
|
||||||
|
authCode = query.Get("auth_code")
|
||||||
|
expiresIn = query.Get("expires_in")
|
||||||
|
)
|
||||||
|
|
||||||
|
agentUserId = ToInt64(query.Get("agent_user_id"))
|
||||||
|
|
||||||
|
pacId = ToUint(query.Get("pac_id"))
|
||||||
|
|
||||||
|
if authCode == "" {
|
||||||
|
return resp, agentUserId, pacId, errors.New("找不到授权码参数")
|
||||||
|
}
|
||||||
|
|
||||||
|
if expiresIn == "" {
|
||||||
|
return resp, agentUserId, pacId, errors.New("找不到过期时间参数")
|
||||||
|
}
|
||||||
|
|
||||||
|
info := app.CgiBinComponentApiQueryAuth(authCode)
|
||||||
|
if info.Result.AuthorizationInfo.AuthorizerAppid == "" {
|
||||||
|
return resp, agentUserId, pacId, errors.New("获取失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
return info.Result, agentUserId, pacId, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFloat64 string到float64
|
||||||
|
func ToFloat64(s string) float64 {
|
||||||
|
i, _ := strconv.ParseFloat(s, 64)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt64 string到int64
|
||||||
|
func ToInt64(s string) int64 {
|
||||||
|
i, err := strconv.ParseInt(s, 10, 64)
|
||||||
|
if err == nil {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
return int64(ToFloat64(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint string到uint64
|
||||||
|
func ToUint(s string) uint {
|
||||||
|
i, err := strconv.ParseUint(s, 10, 64)
|
||||||
|
if err == nil {
|
||||||
|
return uint(i)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sign 微信公众号 url 签名.
|
||||||
|
func Sign(token, timestamp, nonce string) (signature string) {
|
||||||
|
strs := sort.StringSlice{token, timestamp, nonce}
|
||||||
|
strs.Sort()
|
||||||
|
|
||||||
|
buf := make([]byte, 0, len(token)+len(timestamp)+len(nonce))
|
||||||
|
buf = append(buf, strs[0]...)
|
||||||
|
buf = append(buf, strs[1]...)
|
||||||
|
buf = append(buf, strs[2]...)
|
||||||
|
|
||||||
|
hashsum := sha1.Sum(buf)
|
||||||
|
return hex.EncodeToString(hashsum[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgSign 微信公众号/企业号 消息体签名.
|
||||||
|
func MsgSign(token, timestamp, nonce, encryptedMsg string) (signature string) {
|
||||||
|
strs := sort.StringSlice{token, timestamp, nonce, encryptedMsg}
|
||||||
|
strs.Sort()
|
||||||
|
|
||||||
|
h := sha1.New()
|
||||||
|
|
||||||
|
bufw := bufio.NewWriterSize(h, 128) // sha1.BlockSize 的整数倍
|
||||||
|
bufw.WriteString(strs[0])
|
||||||
|
bufw.WriteString(strs[1])
|
||||||
|
bufw.WriteString(strs[2])
|
||||||
|
bufw.WriteString(strs[3])
|
||||||
|
bufw.Flush()
|
||||||
|
|
||||||
|
hashsum := h.Sum(nil)
|
||||||
|
return hex.EncodeToString(hashsum)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckSignature 微信公众号签名检查
|
||||||
|
func CheckSignature(signature, timeStamp, nonce string, token string) bool {
|
||||||
|
paramsArray := []string{token, timeStamp, nonce}
|
||||||
|
// 字典序排序
|
||||||
|
sort.Strings(paramsArray)
|
||||||
|
paramsMsg := ""
|
||||||
|
for _, value := range paramsArray {
|
||||||
|
//fmt.Println(value)
|
||||||
|
paramsMsg += value
|
||||||
|
}
|
||||||
|
//sha1
|
||||||
|
sha1Param := sha1.New()
|
||||||
|
sha1Param.Write([]byte(paramsMsg))
|
||||||
|
msg := hex.EncodeToString(sha1Param.Sum([]byte("")))
|
||||||
|
return msg == signature
|
||||||
|
}
|
||||||
|
|
||||||
|
func AesDecrypt(cipherData []byte, aesKey []byte) ([]byte, error) {
|
||||||
|
k := len(aesKey) //PKCS#7
|
||||||
|
|
||||||
|
if len(cipherData)%k != 0 {
|
||||||
|
return nil, errors.New("crypto/cipher: 密文大小不是aes密钥长度的倍数")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建加密算法实例
|
||||||
|
block, err := aes.NewCipher(aesKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := make([]byte, aes.BlockSize)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 创建加密客户端实例
|
||||||
|
blockMode := cipher.NewCBCDecrypter(block, iv)
|
||||||
|
plainData := make([]byte, len(cipherData))
|
||||||
|
blockMode.CryptBlocks(plainData, cipherData)
|
||||||
|
|
||||||
|
return plainData, nil
|
||||||
|
}
|
@ -0,0 +1,131 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SnsComponentJsCode2sessionResponse struct {
|
||||||
|
Openid string `json:"openid"` // 用户唯一标识的 openid
|
||||||
|
SessionKey string `json:"session_key"` // 会话密钥
|
||||||
|
Unionid string `json:"unionid"` // 用户在开放平台的唯一标识符,在满足 UnionID 下发条件的情况下会返回,详见 UnionID 机制说明。
|
||||||
|
}
|
||||||
|
|
||||||
|
type SnsComponentJsCode2sessionResult struct {
|
||||||
|
Result SnsComponentJsCode2sessionResponse // 结果
|
||||||
|
Body []byte // 内容
|
||||||
|
Err error // 错误
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSnsComponentJsCode2sessionResult(result SnsComponentJsCode2sessionResponse, body []byte, err error) *SnsComponentJsCode2sessionResult {
|
||||||
|
return &SnsComponentJsCode2sessionResult{Result: result, Body: body, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SnsComponentJsCode2session 小程序登录
|
||||||
|
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/others/WeChat_login.html
|
||||||
|
func (app *App) SnsComponentJsCode2session(jsCode string) *SnsComponentJsCode2sessionResult {
|
||||||
|
app.componentAccessToken = app.GetComponentAccessToken()
|
||||||
|
// 参数
|
||||||
|
params := NewParams()
|
||||||
|
params["appid"] = app.AuthorizerAppid // 小程序的 AppID
|
||||||
|
params["js_code"] = jsCode // wx.login 获取的 code
|
||||||
|
params["grant_type"] = "authorization_code" // 填 authorization_code
|
||||||
|
params["component_appid"] = app.ComponentAppId // 第三方平台 appid
|
||||||
|
params["component_access_token"] = app.componentAccessToken // 第三方平台的component_access_token
|
||||||
|
// 请求
|
||||||
|
body, err := app.request("https://api.weixin.qq.com/sns/component/jscode2session", params, http.MethodGet)
|
||||||
|
// 定义
|
||||||
|
var response SnsComponentJsCode2sessionResponse
|
||||||
|
err = json.Unmarshal(body, &response)
|
||||||
|
return NewSnsComponentJsCode2sessionResult(response, body, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserInfo struct {
|
||||||
|
EncryptedData string `json:"encrypted_data"`
|
||||||
|
Iv string `json:"iv"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserInfoResponse struct {
|
||||||
|
OpenId string `json:"openId"`
|
||||||
|
NickName string `json:"nickName"`
|
||||||
|
Gender int `json:"gender"`
|
||||||
|
City string `json:"city"`
|
||||||
|
Province string `json:"province"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
AvatarUrl string `json:"avatarUrl"`
|
||||||
|
UnionId string `json:"unionId"`
|
||||||
|
Watermark struct {
|
||||||
|
AppID string `json:"appid"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
} `json:"watermark"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserInfoResult struct {
|
||||||
|
Result UserInfoResponse // 结果
|
||||||
|
Err error // 错误
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserInfoResult(result UserInfoResponse, err error) *UserInfoResult {
|
||||||
|
return &UserInfoResult{Result: result, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserInfo 解密用户信息
|
||||||
|
func (r *SnsComponentJsCode2sessionResult) UserInfo(param UserInfo) *UserInfoResult {
|
||||||
|
var response UserInfoResponse
|
||||||
|
aesKey, err := base64.StdEncoding.DecodeString(r.Result.SessionKey)
|
||||||
|
if err != nil {
|
||||||
|
return NewUserInfoResult(response, err)
|
||||||
|
}
|
||||||
|
cipherText, err := base64.StdEncoding.DecodeString(param.EncryptedData)
|
||||||
|
if err != nil {
|
||||||
|
return NewUserInfoResult(response, err)
|
||||||
|
}
|
||||||
|
ivBytes, err := base64.StdEncoding.DecodeString(param.Iv)
|
||||||
|
if err != nil {
|
||||||
|
return NewUserInfoResult(response, err)
|
||||||
|
}
|
||||||
|
block, err := aes.NewCipher(aesKey)
|
||||||
|
if err != nil {
|
||||||
|
return NewUserInfoResult(response, err)
|
||||||
|
}
|
||||||
|
mode := cipher.NewCBCDecrypter(block, ivBytes)
|
||||||
|
mode.CryptBlocks(cipherText, cipherText)
|
||||||
|
cipherText, err = r.pkcs7Unpaid(cipherText, block.BlockSize())
|
||||||
|
if err != nil {
|
||||||
|
return NewUserInfoResult(response, err)
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(cipherText, &response)
|
||||||
|
if err != nil {
|
||||||
|
return NewUserInfoResult(response, err)
|
||||||
|
}
|
||||||
|
return NewUserInfoResult(response, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserInfoResponse) UserInfoAvatarUrlReal() string {
|
||||||
|
return strings.Replace(u.AvatarUrl, "/132", "/0", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SnsComponentJsCode2sessionResult) pkcs7Unpaid(data []byte, blockSize int) ([]byte, error) {
|
||||||
|
if blockSize <= 0 {
|
||||||
|
return nil, errors.New("invalid block size")
|
||||||
|
}
|
||||||
|
if len(data)%blockSize != 0 || len(data) == 0 {
|
||||||
|
return nil, errors.New("invalid PKCS7 data")
|
||||||
|
}
|
||||||
|
c := data[len(data)-1]
|
||||||
|
n := int(c)
|
||||||
|
if n == 0 || n > len(data) {
|
||||||
|
return nil, errors.New("invalid padding on input")
|
||||||
|
}
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
if data[len(data)-n+i] != c {
|
||||||
|
return nil, errors.New("invalid padding on input")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data[:len(data)-n], nil
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
package wechatopen
|
@ -0,0 +1,53 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WxaBindTesterResponse struct {
|
||||||
|
Errcode int `json:"errcode"` // 错误码
|
||||||
|
Errmsg string `json:"errmsg"` // 错误信息
|
||||||
|
Userstr string `json:"userstr"` // 人员对应的唯一字符串
|
||||||
|
}
|
||||||
|
|
||||||
|
type WxaBindTesterResult struct {
|
||||||
|
Result WxaBindTesterResponse // 结果
|
||||||
|
Body []byte // 内容
|
||||||
|
Err error // 错误
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWxaBindTesterResult(result WxaBindTesterResponse, body []byte, err error) *WxaBindTesterResult {
|
||||||
|
return &WxaBindTesterResult{Result: result, Body: body, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WxaBindTester 绑定微信用户为体验者
|
||||||
|
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/Mini_Program_AdminManagement/Admin.html
|
||||||
|
func (app *App) WxaBindTester(wechatid string) *WxaBindTesterResult {
|
||||||
|
app.authorizerAccessToken = app.GetAuthorizerAccessToken()
|
||||||
|
// 参数
|
||||||
|
params := NewParams()
|
||||||
|
params["wechatid"] = wechatid
|
||||||
|
// 请求
|
||||||
|
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/wxa/bind_tester?access_token=%s", app.authorizerAccessToken), params, http.MethodPost)
|
||||||
|
// 定义
|
||||||
|
var response WxaBindTesterResponse
|
||||||
|
err = json.Unmarshal(body, &response)
|
||||||
|
return NewWxaBindTesterResult(response, body, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrcodeInfo 错误描述
|
||||||
|
func (resp *WxaBindTesterResult) ErrcodeInfo() string {
|
||||||
|
switch resp.Result.Errcode {
|
||||||
|
case 85001:
|
||||||
|
return "微信号不存在或微信号设置为不可搜索"
|
||||||
|
case 85002:
|
||||||
|
return "小程序绑定的体验者数量达到上限"
|
||||||
|
case 85003:
|
||||||
|
return "微信号绑定的小程序体验者达到上限"
|
||||||
|
case 85004:
|
||||||
|
return "微信号已经绑定"
|
||||||
|
}
|
||||||
|
return "系统繁忙"
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WxaGetAuditStatusResponse struct {
|
||||||
|
Errcode int `json:"errcode"` // 返回码
|
||||||
|
Errmsg string `json:"errmsg"` // 错误信息
|
||||||
|
Auditid int `json:"auditid"` // 最新的审核 ID
|
||||||
|
Status int `json:"status"` // 审核状态
|
||||||
|
Reason string `json:"reason"` // 当审核被拒绝时,返回的拒绝原因
|
||||||
|
ScreenShot string `json:"ScreenShot"` // 当审核被拒绝时,会返回审核失败的小程序截图示例。用 | 分隔的 media_id 的列表,可通过获取永久素材接口拉取截图内容
|
||||||
|
}
|
||||||
|
|
||||||
|
type WxaGetAuditStatusResult struct {
|
||||||
|
Result WxaGetAuditStatusResponse // 结果
|
||||||
|
Body []byte // 内容
|
||||||
|
Err error // 错误
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWxaGetAuditStatusResult(result WxaGetAuditStatusResponse, body []byte, err error) *WxaGetAuditStatusResult {
|
||||||
|
return &WxaGetAuditStatusResult{Result: result, Body: body, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WxaGetAuditStatus 查询指定发布审核单的审核状态
|
||||||
|
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/code/get_auditstatus.html
|
||||||
|
func (app *App) WxaGetAuditStatus(auditid int64) *WxaGetAuditStatusResult {
|
||||||
|
app.authorizerAccessToken = app.GetAuthorizerAccessToken()
|
||||||
|
// 参数
|
||||||
|
params := app.NewParamsWith()
|
||||||
|
params.Set("auditid", auditid)
|
||||||
|
// 请求
|
||||||
|
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/wxa/get_auditstatus?access_token=%s", app.authorizerAccessToken), params, http.MethodPost)
|
||||||
|
// 定义
|
||||||
|
var response WxaGetAuditStatusResponse
|
||||||
|
err = json.Unmarshal(body, &response)
|
||||||
|
return NewWxaGetAuditStatusResult(response, body, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrcodeInfo 错误描述
|
||||||
|
func (resp *WxaGetAuditStatusResult) ErrcodeInfo() string {
|
||||||
|
switch resp.Result.Errcode {
|
||||||
|
case 86000:
|
||||||
|
return "不是由第三方代小程序进行调用"
|
||||||
|
case 86001:
|
||||||
|
return "不存在第三方的已经提交的代码"
|
||||||
|
case 85012:
|
||||||
|
return "无效的审核 id"
|
||||||
|
}
|
||||||
|
return "系统繁忙"
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WxaGetLatestAuditStatusResponse struct {
|
||||||
|
Errcode int `json:"errcode"` // 返回码
|
||||||
|
Errmsg string `json:"errmsg"` // 错误信息
|
||||||
|
Auditid int `json:"auditid"` // 最新的审核 ID
|
||||||
|
Status int `json:"status"` // 审核状态
|
||||||
|
Reason string `json:"reason"` // 当审核被拒绝时,返回的拒绝原因
|
||||||
|
ScreenShot string `json:"ScreenShot"` // 当审核被拒绝时,会返回审核失败的小程序截图示例。用 | 分隔的 media_id 的列表,可通过获取永久素材接口拉取截图内容
|
||||||
|
}
|
||||||
|
|
||||||
|
type WxaGetLatestAuditStatusResult struct {
|
||||||
|
Result WxaGetLatestAuditStatusResponse // 结果
|
||||||
|
Body []byte // 内容
|
||||||
|
Err error // 错误
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWxaGetLatestAuditStatusResult(result WxaGetLatestAuditStatusResponse, body []byte, err error) *WxaGetLatestAuditStatusResult {
|
||||||
|
return &WxaGetLatestAuditStatusResult{Result: result, Body: body, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WxaGetLatestAuditStatus 查询最新一次提交的审核状态
|
||||||
|
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/code/get_auditstatus.html
|
||||||
|
func (app *App) WxaGetLatestAuditStatus() *WxaGetLatestAuditStatusResult {
|
||||||
|
app.authorizerAccessToken = app.GetAuthorizerAccessToken()
|
||||||
|
// 请求
|
||||||
|
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/wxa/get_latest_auditstatus?access_token=%s", app.authorizerAccessToken), map[string]interface{}{}, http.MethodPost)
|
||||||
|
// 定义
|
||||||
|
var response WxaGetLatestAuditStatusResponse
|
||||||
|
err = json.Unmarshal(body, &response)
|
||||||
|
return NewWxaGetLatestAuditStatusResult(response, body, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrcodeInfo 错误描述
|
||||||
|
func (resp *WxaGetLatestAuditStatusResult) ErrcodeInfo() string {
|
||||||
|
switch resp.Result.Errcode {
|
||||||
|
case 86000:
|
||||||
|
return "不是由第三方代小程序进行调用"
|
||||||
|
case 86001:
|
||||||
|
return "不存在第三方的已经提交的代码"
|
||||||
|
case 85012:
|
||||||
|
return "无效的审核 id"
|
||||||
|
}
|
||||||
|
return "系统繁忙"
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WxaGetPageResponse struct {
|
||||||
|
Errcode int `json:"errcode"`
|
||||||
|
Errmsg string `json:"errmsg"`
|
||||||
|
PageList []string `json:"page_list"` // page_list 页面配置列表
|
||||||
|
}
|
||||||
|
|
||||||
|
type WxaGetPageResult struct {
|
||||||
|
Result WxaGetPageResponse // 结果
|
||||||
|
Body []byte // 内容
|
||||||
|
Err error // 错误
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWxaGetPageResult(result WxaGetPageResponse, body []byte, err error) *WxaGetPageResult {
|
||||||
|
return &WxaGetPageResult{Result: result, Body: body, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WxaGetPage 获取已上传的代码的页面列表
|
||||||
|
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/code/get_page.html
|
||||||
|
func (app *App) WxaGetPage() *WxaGetPageResult {
|
||||||
|
app.authorizerAccessToken = app.GetAuthorizerAccessToken()
|
||||||
|
// 请求
|
||||||
|
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/wxa/get_page?access_token=%s", app.authorizerAccessToken), map[string]interface{}{}, http.MethodGet)
|
||||||
|
// 定义
|
||||||
|
var response WxaGetPageResponse
|
||||||
|
err = json.Unmarshal(body, &response)
|
||||||
|
return NewWxaGetPageResult(response, body, err)
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WxaGetQrcodeResponse struct {
|
||||||
|
Errcode int `json:"errcode"`
|
||||||
|
Errmsg string `json:"errmsg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WxaGetQrcodeResult struct {
|
||||||
|
Result WxaGetQrcodeResponse // 结果
|
||||||
|
Body []byte // 内容
|
||||||
|
Err error // 错误
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWxaGetQrcodeResult(result WxaGetQrcodeResponse, body []byte, err error) *WxaGetQrcodeResult {
|
||||||
|
return &WxaGetQrcodeResult{Result: result, Body: body, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WxaGetQrcode 获取体验版二维码
|
||||||
|
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/code/get_qrcode.html
|
||||||
|
func (app *App) WxaGetQrcode(path string) *WxaGetQrcodeResult {
|
||||||
|
app.authorizerAccessToken = app.GetAuthorizerAccessToken()
|
||||||
|
// 参数
|
||||||
|
params := NewParams()
|
||||||
|
if path != "" {
|
||||||
|
params["path"] = path // 指定二维码扫码后直接进入指定页面并可同时带上参数)
|
||||||
|
}
|
||||||
|
// 请求
|
||||||
|
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/wxa/get_qrcode?access_token=%s", app.authorizerAccessToken), params, http.MethodGet)
|
||||||
|
// 定义
|
||||||
|
var response WxaGetQrcodeResponse
|
||||||
|
err = json.Unmarshal(body, &response)
|
||||||
|
return NewWxaGetQrcodeResult(response, body, err)
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WxaGetTemplateDraftListResponse struct {
|
||||||
|
Errcode int `json:"errcode"` // 返回码
|
||||||
|
Errmsg string `json:"errmsg"` // 错误信息
|
||||||
|
DraftList []struct {
|
||||||
|
CreateTime int `json:"create_time"` // 开发者上传草稿时间戳
|
||||||
|
UserVersion string `json:"user_version"` // 版本号,开发者自定义字段
|
||||||
|
UserDesc string `json:"user_desc"` // 版本描述 开发者自定义字段
|
||||||
|
DraftId int `json:"draft_id"` // 草稿 id
|
||||||
|
SourceMiniprogramAppid string `json:"source_miniprogram_appid"`
|
||||||
|
SourceMiniprogram string `json:"source_miniprogram"`
|
||||||
|
Developer string `json:"developer"`
|
||||||
|
CategoryList []interface{} `json:"category_list"`
|
||||||
|
} `json:"draft_list"` // 草稿信息列表
|
||||||
|
}
|
||||||
|
|
||||||
|
type WxaGetTemplateDraftListResult struct {
|
||||||
|
Result WxaGetTemplateDraftListResponse // 结果
|
||||||
|
Body []byte // 内容
|
||||||
|
Err error // 错误
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWxaGetTemplateDraftListResult(result WxaGetTemplateDraftListResponse, body []byte, err error) *WxaGetTemplateDraftListResult {
|
||||||
|
return &WxaGetTemplateDraftListResult{Result: result, Body: body, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WxaGetTemplateDraftList 获取代码草稿列表
|
||||||
|
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/ThirdParty/code_template/gettemplatedraftlist.html
|
||||||
|
func (app *App) WxaGetTemplateDraftList() *WxaGetTemplateDraftListResult {
|
||||||
|
app.componentAccessToken = app.GetComponentAccessToken()
|
||||||
|
// 请求
|
||||||
|
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/wxa/gettemplatedraftlist?access_token=%s", app.componentAccessToken), map[string]interface{}{}, http.MethodGet)
|
||||||
|
// 定义
|
||||||
|
var response WxaGetTemplateDraftListResponse
|
||||||
|
err = json.Unmarshal(body, &response)
|
||||||
|
return NewWxaGetTemplateDraftListResult(response, body, err)
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WxaMemberAuthResponse struct {
|
||||||
|
Errcode int `json:"errcode"` // 错误码
|
||||||
|
Errmsg string `json:"errmsg"` // 错误信息
|
||||||
|
Members []struct {
|
||||||
|
Userstr string `json:"userstr"` // 人员对应的唯一字符串
|
||||||
|
} `json:"members"` // 人员信息列表
|
||||||
|
}
|
||||||
|
|
||||||
|
type WxaMemberAuthResult struct {
|
||||||
|
Result WxaMemberAuthResponse // 结果
|
||||||
|
Body []byte // 内容
|
||||||
|
Err error // 错误
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWxaMemberAuthResult(result WxaMemberAuthResponse, body []byte, err error) *WxaMemberAuthResult {
|
||||||
|
return &WxaMemberAuthResult{Result: result, Body: body, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WxaMemberAuth 获取体验者列表
|
||||||
|
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/Mini_Program_AdminManagement/memberauth.html
|
||||||
|
func (app *App) WxaMemberAuth() *WxaMemberAuthResult {
|
||||||
|
app.authorizerAccessToken = app.GetAuthorizerAccessToken()
|
||||||
|
// 参数
|
||||||
|
params := NewParams()
|
||||||
|
params["action"] = "get_experiencer"
|
||||||
|
// 请求
|
||||||
|
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/wxa/memberauth?access_token=%s", app.authorizerAccessToken), params, http.MethodPost)
|
||||||
|
// 定义
|
||||||
|
var response WxaMemberAuthResponse
|
||||||
|
err = json.Unmarshal(body, &response)
|
||||||
|
return NewWxaMemberAuthResult(response, body, err)
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WxaModifyDomainResponse struct {
|
||||||
|
Errcode int `json:"errcode"` // 错误码
|
||||||
|
Errmsg string `json:"errmsg"` // 错误信息
|
||||||
|
Requestdomain []string `json:"requestdomain"` // request 合法域名
|
||||||
|
Wsrequestdomain []string `json:"wsrequestdomain"` // socket 合法域名
|
||||||
|
Uploaddomain []string `json:"uploaddomain"` // uploadFile 合法域名
|
||||||
|
Downloaddomain []string `json:"downloaddomain"` // downloadFile 合法域名
|
||||||
|
Udpdomain []string `json:"udpdomain"` // udp 合法域名
|
||||||
|
Tcpdomain []string `json:"tcpdomain"` // tcp 合法域名
|
||||||
|
InvalidRequestdomain []string `json:"invalid_requestdomain"` // request 不合法域名
|
||||||
|
InvalidWsrequestdomain []string `json:"invalid_wsrequestdomain"` // socket 不合法域名
|
||||||
|
InvalidUploaddomain []string `json:"invalid_uploaddomain"` // uploadFile 不合法域名
|
||||||
|
InvalidDownloaddomain []string `json:"invalid_downloaddomain"` // downloadFile 不合法域名
|
||||||
|
InvalidUdpdomain []string `json:"invalid_udpdomain"` // udp 不合法域名
|
||||||
|
InvalidTcpdomain []string `json:"invalid_tcpdomain"` // tcp 不合法域名
|
||||||
|
NoIcpDomain []string `json:"no_icp_domain"` // 没有经过icp备案的域名
|
||||||
|
}
|
||||||
|
|
||||||
|
type WxaModifyDomainResult struct {
|
||||||
|
Result WxaModifyDomainResponse // 结果
|
||||||
|
Body []byte // 内容
|
||||||
|
Err error // 错误
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWxaModifyDomainResult(result WxaModifyDomainResponse, body []byte, err error) *WxaModifyDomainResult {
|
||||||
|
return &WxaModifyDomainResult{Result: result, Body: body, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WxaModifyDomain 设置服务器域名
|
||||||
|
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/Mini_Program_Basic_Info/Server_Address_Configuration.html
|
||||||
|
func (app *App) WxaModifyDomain(notMustParams ...Params) *WxaModifyDomainResult {
|
||||||
|
app.authorizerAccessToken = app.GetAuthorizerAccessToken()
|
||||||
|
// 参数
|
||||||
|
params := app.NewParamsWith(notMustParams...)
|
||||||
|
// 请求
|
||||||
|
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/wxa/modify_domain?access_token=%s", app.authorizerAccessToken), params, http.MethodPost)
|
||||||
|
// 定义
|
||||||
|
var response WxaModifyDomainResponse
|
||||||
|
err = json.Unmarshal(body, &response)
|
||||||
|
return NewWxaModifyDomainResult(response, body, err)
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
package wechatopen
|
@ -0,0 +1 @@
|
|||||||
|
package wechatopen
|
@ -0,0 +1,36 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WxaSubmitAuditResponse struct {
|
||||||
|
Errcode int `json:"errcode"`
|
||||||
|
Errmsg string `json:"errmsg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WxaSubmitAuditResult struct {
|
||||||
|
Result WxaSubmitAuditResponse // 结果
|
||||||
|
Body []byte // 内容
|
||||||
|
Err error // 错误
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWxaSubmitAuditResult(result WxaSubmitAuditResponse, body []byte, err error) *WxaSubmitAuditResult {
|
||||||
|
return &WxaSubmitAuditResult{Result: result, Body: body, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WxaSubmitAudit 提交审核
|
||||||
|
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/code/submit_audit.html
|
||||||
|
func (app *App) WxaSubmitAudit(notMustParams ...Params) *WxaSubmitAuditResult {
|
||||||
|
app.authorizerAccessToken = app.GetAuthorizerAccessToken()
|
||||||
|
// 参数
|
||||||
|
params := app.NewParamsWith(notMustParams...)
|
||||||
|
// 请求
|
||||||
|
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/wxa/submit_audit?access_token=%s", app.authorizerAccessToken), params, http.MethodPost)
|
||||||
|
// 定义
|
||||||
|
var response WxaSubmitAuditResponse
|
||||||
|
err = json.Unmarshal(body, &response)
|
||||||
|
return NewWxaSubmitAuditResult(response, body, err)
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package wechatopen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WxaUnbindTesterResponse struct {
|
||||||
|
Errcode int `json:"errcode"` // 错误码
|
||||||
|
Errmsg string `json:"errmsg"` // 错误信息
|
||||||
|
}
|
||||||
|
|
||||||
|
type WxaUnbindTesterResult struct {
|
||||||
|
Result WxaUnbindTesterResponse // 结果
|
||||||
|
Body []byte // 内容
|
||||||
|
Err error // 错误
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWxaUnbindTesterResult(result WxaUnbindTesterResponse, body []byte, err error) *WxaUnbindTesterResult {
|
||||||
|
return &WxaUnbindTesterResult{Result: result, Body: body, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WxaUnbindTester 解除绑定体验者
|
||||||
|
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/Mini_Program_AdminManagement/unbind_tester.html
|
||||||
|
func (app *App) WxaUnbindTester(wechatid, userstr string) *WxaUnbindTesterResult {
|
||||||
|
app.authorizerAccessToken = app.GetAuthorizerAccessToken()
|
||||||
|
// 参数
|
||||||
|
params := NewParams()
|
||||||
|
if wechatid != "" {
|
||||||
|
params["wechatid"] = wechatid
|
||||||
|
}
|
||||||
|
params["userstr"] = userstr
|
||||||
|
// 请求
|
||||||
|
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/wxa/unbind_tester?access_token=%s", app.authorizerAccessToken), params, http.MethodPost)
|
||||||
|
// 定义
|
||||||
|
var response WxaUnbindTesterResponse
|
||||||
|
err = json.Unmarshal(body, &response)
|
||||||
|
return NewWxaUnbindTesterResult(response, body, err)
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
package wechatopen
|
Loading…
Reference in new issue