master
李光春 2 years ago
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
.gitignore vendored

@ -4,6 +4,7 @@
.idea
.vscode
*.log
.github
gitmod.sh
/service/*/*_test.go
/utils/*/*_test.go

@ -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,48 @@
package wechatopen
import (
"gitee.com/dtapps/go-library/utils/gotime"
"gorm.io/gorm"
"time"
)
// GetAuthorizerAccessTokenMonitor 获取获取/刷新接口调用令牌和监控
func (app *App) GetAuthorizerAccessTokenMonitor() string {
// 查询
authorizerAccessToken := app.GetAuthorizerAccessToken()
if authorizerAccessToken != "" {
return authorizerAccessToken
}
// 重新获取
return app.SetAuthorizerAccessToken(app.CgiBinComponentApiAuthorizerToken()).AuthorizerAccessToken
}
// SetAuthorizerAccessToken 设置获取/刷新接口调用令牌和自动获取
func (app *App) SetAuthorizerAccessToken(info *CgiBinComponentApiAuthorizerTokenResult) CgiBinComponentApiAuthorizerTokenResponse {
if app.Db == nil || info.Result.AuthorizerAccessToken == "" || info.Result.AuthorizerRefreshToken == "" || info.authorizerAppid == "" {
return CgiBinComponentApiAuthorizerTokenResponse{}
}
app.Db.Create(&AuthorizerAccessToken{
ComponentAppId: app.ComponentAppId,
AuthorizerAppId: info.authorizerAppid,
AuthorizerAccessToken: info.Result.AuthorizerAccessToken,
AuthorizerRefreshToken: info.Result.AuthorizerRefreshToken,
ExpiresIn: info.Result.ExpiresIn,
ExpireTime: gotime.Current().AfterHour(2).Time,
})
return info.Result
}
type AuthorizerAccessToken struct {
gorm.Model
ComponentAppId string `json:"component_app_id"` // 第三方平台 appid
AuthorizerAppId string `json:"authorizer_app_id"` // 授权方 appid
AuthorizerAccessToken string `json:"authorizer_access_token"` // 接口调用令牌(在授权的公众号/小程序具备 API 权限时,才有此返回值)
AuthorizerRefreshToken string `json:"authorizer_refresh_token"` // 刷新令牌在授权的公众号具备API权限时才有此返回值刷新令牌主要用于第三方平台获取和刷新已授权用户的 authorizer_access_token。一旦丢失只能让用户重新授权才能再次拿到新的刷新令牌。用户重新授权后之前的刷新令牌会失效
ExpiresIn int64 `json:"expires_in"` // 有效期,单位:秒
ExpireTime time.Time `json:"expire_time"` // 过期时间
}
func (m *AuthorizerAccessToken) TableName() string {
return "authorizer_access_token"
}

@ -0,0 +1,64 @@
package wechatopen
import (
"encoding/json"
"fmt"
"net/http"
)
type CgiBinAccountGetAccountBasicInfoResponse struct {
Errcode int `json:"errcode"` // 返回码
Errmsg string `json:"errmsg"` // 错误信息
Appid string `json:"appid"` // 帐号 appid
AccountType int `json:"account_type"` // 帐号类型1订阅号2服务号3小程序
PrincipalType int `json:"principal_type"` // 主体类型
PrincipalName string `json:"principal_name"` // 主体名称
Credential string `json:"credential"` // 主体标识
RealnameStatus int `json:"realname_status"` // 实名验证状态 1=实名验证成功 2=实名验证中 3=实名验证失败
WxVerifyInfo struct {
QualificationVerify bool `json:"qualification_verify"` // 是否资质认证,若是,拥有微信认证相关的权限
NamingVerify bool `json:"naming_verify"` // 是否名称认证
AnnualReview bool `json:"annual_review"` // 是否需要年审qualification_verify == true 时才有该字段)
AnnualReviewBeginTime int `json:"annual_review_begin_time"` // 年审开始时间时间戳qualification_verify == true 时才有该字段)
AnnualReviewEndTime int `json:"annual_review_end_time"` // 年审截止时间时间戳qualification_verify == true 时才有该字段)
} `json:"wx_verify_info"` // 微信认证信息
SignatureInfo struct {
Signature string `json:"signature"` // 功能介绍
ModifyUsedCount int `json:"modify_used_count"` // 功能介绍已使用修改次数(本月)
ModifyQuota int `json:"modify_quota"` // 功能介绍修改次数总额度(本月)
} `json:"signature_info"` // 功能介绍信息
HeadImageInfo struct {
HeadImageUrl string `json:"head_image_url"` // 头像 url
ModifyUsedCount int `json:"modify_used_count"` // 头像已使用修改次数(本年)
ModifyQuota int `json:"modify_quota"` // 头像修改次数总额度(本年)
} `json:"head_image_info"` // 头像信息
NicknameInfo struct {
Nickname string `json:"nickname"` // 小程序名称
ModifyUsedCount int `json:"modify_used_count"` // 小程序名称已使用修改次数(本年)
ModifyQuota int `json:"modify_quota"` // 小程序名称修改次数总额度(本年)
} `json:"nickname_info"` // 名称信息
RegisteredCountry int `json:"registered_country"` // 注册国家
Nickname string `json:"nickname"` // 小程序名称
}
type CgiBinAccountGetAccountBasicInfoResult struct {
Result CgiBinAccountGetAccountBasicInfoResponse // 结果
Body []byte // 内容
Err error // 错误
}
func NewCgiBinAccountGetAccountBasicInfoResult(result CgiBinAccountGetAccountBasicInfoResponse, body []byte, err error) *CgiBinAccountGetAccountBasicInfoResult {
return &CgiBinAccountGetAccountBasicInfoResult{Result: result, Body: body, Err: err}
}
// CgiBinAccountGetAccountBasicInfo 获取基本信息
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/Mini_Program_Basic_Info/Mini_Program_Information_Settings.html
func (app *App) CgiBinAccountGetAccountBasicInfo() *CgiBinAccountGetAccountBasicInfoResult {
app.authorizerAccessToken = app.GetAuthorizerAccessToken()
// 请求
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/account/getaccountbasicinfo?access_token=%v", app.authorizerAccessToken), map[string]interface{}{}, http.MethodGet)
// 定义
var response CgiBinAccountGetAccountBasicInfoResponse
err = json.Unmarshal(body, &response)
return NewCgiBinAccountGetAccountBasicInfoResult(response, body, err)
}

@ -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,97 @@
package wechatopen
import (
"encoding/json"
"fmt"
"net/http"
)
type CgiBinComponentApiGetAuthorizerInfoResponse struct {
AuthorizerInfo struct {
NickName string `json:"nick_name"` // 昵称
HeadImg string `json:"head_img"` // 头像
ServiceTypeInfo struct {
Id int `json:"id"` // 0=普通小程序 2=门店小程序 3=门店小程序 4=小游戏 10=小商店 12=试用小程序
} `json:"service_type_info"` // 小程序类型
VerifyTypeInfo struct {
Id int `json:"id"` // -1=未认证 0=微信认证
} `json:"verify_type_info"` // 小程序认证类型
UserName string `json:"user_name"` // 原始 ID
PrincipalName string `json:"principal_name"` // 主体名称
Signature string `json:"signature"` // 帐号介绍
BusinessInfo struct {
OpenPay int `json:"open_pay"`
OpenShake int `json:"open_shake"`
OpenScan int `json:"open_scan"`
OpenCard int `json:"open_card"`
OpenStore int `json:"open_store"`
} `json:"business_info"` // 用以了解功能的开通状况0代表未开通1代表已开通)
QrcodeUrl string `json:"qrcode_url"` // 二维码图片的 URL开发者最好自行也进行保存
MiniProgramInfo struct {
Network struct {
RequestDomain []string `json:"RequestDomain"`
WsRequestDomain []string `json:"WsRequestDomain"`
UploadDomain []string `json:"UploadDomain"`
DownloadDomain []string `json:"DownloadDomain"`
BizDomain []string `json:"BizDomain"`
UDPDomain []string `json:"UDPDomain"`
TCPDomain []interface{} `json:"TCPDomain"`
NewRequestDomain []interface{} `json:"NewRequestDomain"`
NewWsRequestDomain []interface{} `json:"NewWsRequestDomain"`
NewUploadDomain []interface{} `json:"NewUploadDomain"`
NewDownloadDomain []interface{} `json:"NewDownloadDomain"`
NewBizDomain []interface{} `json:"NewBizDomain"`
NewUDPDomain []interface{} `json:"NewUDPDomain"`
NewTCPDomain []interface{} `json:"NewTCPDomain"`
} `json:"network"` // 小程序配置的合法域名信息
Categories []struct {
First string `json:"first"`
Second string `json:"second"`
} `json:"categories"` // 小程序配置的类目信息
VisitStatus int `json:"visit_status"`
} `json:"MiniProgramInfo"` // 小程序配置,根据这个字段判断是否为小程序类型授权
Alias string `json:"alias"` // 公众号所设置的微信号,可能为空
Idc int `json:"idc"`
} `json:"authorizer_info"` // 小程序帐号信息
AuthorizationInfo struct {
AuthorizerAppid string `json:"authorizer_appid"` // 授权方 appid
FuncInfo []struct {
FuncscopeCategory struct {
Id int `json:"id"`
} `json:"funcscope_category"`
ConfirmInfo struct {
NeedConfirm int `json:"need_confirm"`
AlreadyConfirm int `json:"already_confirm"`
CanConfirm int `json:"can_confirm"`
} `json:"confirm_info,omitempty"`
} `json:"func_info"` // 授权给开发者的权限集列表
AuthorizerRefreshToken string `json:"authorizer_refresh_token"`
} `json:"authorization_info"` // 授权信息
}
type CgiBinComponentApiGetAuthorizerInfoResult struct {
Result CgiBinComponentApiGetAuthorizerInfoResponse // 结果
Body []byte // 内容
Err error // 错误
}
func NewCgiBinComponentApiGetAuthorizerInfoResult(result CgiBinComponentApiGetAuthorizerInfoResponse, body []byte, err error) *CgiBinComponentApiGetAuthorizerInfoResult {
return &CgiBinComponentApiGetAuthorizerInfoResult{Result: result, Body: body, Err: err}
}
// CgiBinComponentApiGetAuthorizerInfo 获取授权帐号详情
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/ThirdParty/token/api_get_authorizer_info.html
func (app *App) CgiBinComponentApiGetAuthorizerInfo() *CgiBinComponentApiGetAuthorizerInfoResult {
app.componentAccessToken = app.GetComponentAccessToken()
// 参数
param := NewParams()
param["component_appid"] = app.ComponentAppId // 第三方平台 appid
param["authorizer_appid"] = app.AuthorizerAppid // 授权方 appid
params := app.NewParamsWith(param)
// 请求
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_info?component_access_token=%v", app.componentAccessToken), params, http.MethodPost)
// 定义
var response CgiBinComponentApiGetAuthorizerInfoResponse
err = json.Unmarshal(body, &response)
return NewCgiBinComponentApiGetAuthorizerInfoResult(response, body, err)
}

@ -0,0 +1,53 @@
package wechatopen
import (
"encoding/json"
"fmt"
"net/http"
)
type CgiBinComponentApiQueryAuthResponse struct {
AuthorizationInfo struct {
AuthorizerAppid string `json:"authorizer_appid"` // 授权方 appid
AuthorizerAccessToken string `json:"authorizer_access_token"` // 接口调用令牌(在授权的公众号/小程序具备 API 权限时,才有此返回值)
ExpiresIn int64 `json:"expires_in"` // authorizer_access_token 的有效期(在授权的公众号/小程序具备API权限时才有此返回值单位
AuthorizerRefreshToken string `json:"authorizer_refresh_token"` // 刷新令牌在授权的公众号具备API权限时才有此返回值刷新令牌主要用于第三方平台获取和刷新已授权用户的 authorizer_access_token。一旦丢失只能让用户重新授权才能再次拿到新的刷新令牌。用户重新授权后之前的刷新令牌会失效
FuncInfo []struct {
FuncscopeCategory struct {
Id int `json:"id"`
} `json:"funcscope_category"`
ConfirmInfo struct {
NeedConfirm int `json:"need_confirm"`
AlreadyConfirm int `json:"already_confirm"`
CanConfirm int `json:"can_confirm"`
} `json:"confirm_info,omitempty"`
} `json:"func_info"`
} `json:"authorization_info"`
}
type CgiBinComponentApiQueryAuthResult struct {
Result CgiBinComponentApiQueryAuthResponse // 结果
Body []byte // 内容
Err error // 错误
}
func NewCgiBinComponentApiQueryAuthResult(result CgiBinComponentApiQueryAuthResponse, body []byte, err error) *CgiBinComponentApiQueryAuthResult {
return &CgiBinComponentApiQueryAuthResult{Result: result, Body: body, Err: err}
}
// CgiBinComponentApiQueryAuth 使用授权码获取授权信息
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/ThirdParty/token/authorization_info.html
func (app *App) CgiBinComponentApiQueryAuth(authorizationCode string) *CgiBinComponentApiQueryAuthResult {
app.componentAccessToken = app.GetComponentAccessToken()
// 参数
param := NewParams()
param["component_appid"] = app.ComponentAppId // 第三方平台 appid
param["authorization_code"] = authorizationCode // 授权码, 会在授权成功时返回给第三方平台
params := app.NewParamsWith(param)
// 请求
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token=%v", app.componentAccessToken), params, http.MethodPost)
// 定义
var response CgiBinComponentApiQueryAuthResponse
err = json.Unmarshal(body, &response)
return NewCgiBinComponentApiQueryAuthResult(response, body, err)
}

@ -0,0 +1,39 @@
package wechatopen
import (
"encoding/json"
"net/http"
)
type CgiBinComponentApiStartPushTicketResponse struct {
AccessToken string `json:"access_token"` // 获取到的凭证
ExpiresIn int `json:"expires_in"` // 凭证有效时间单位秒。目前是7200秒之内的值
Errcode int `json:"errcode"` // 错误码
Errmsg string `json:"errmsg"` // 错误信息
}
type CgiBinComponentApiStartPushTicketResult struct {
Result CgiBinComponentApiStartPushTicketResponse // 结果
Body []byte // 内容
Err error // 错误
}
func NewCgiBinComponentApiStartPushTicketResult(result CgiBinComponentApiStartPushTicketResponse, body []byte, err error) *CgiBinComponentApiStartPushTicketResult {
return &CgiBinComponentApiStartPushTicketResult{Result: result, Body: body, Err: err}
}
// CgiBinComponentApiStartPushTicket 启动ticket推送服务
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/ThirdParty/token/component_verify_ticket_service.html
func (app *App) CgiBinComponentApiStartPushTicket() *CgiBinComponentApiStartPushTicketResult {
// 参数
param := NewParams()
param["component_appid"] = app.ComponentAppId // 平台型第三方平台的appid
param["component_secret"] = app.ComponentAppSecret // 平台型第三方平台的APPSECRET
params := app.NewParamsWith(param)
// 请求
body, err := app.request("https://api.weixin.qq.com/cgi-bin/component/api_start_push_ticket", params, http.MethodPost)
// 定义
var response CgiBinComponentApiStartPushTicketResponse
err = json.Unmarshal(body, &response)
return NewCgiBinComponentApiStartPushTicketResult(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,35 @@
package wechatopen
import (
"gitee.com/dtapps/go-library/utils/gotime"
"gorm.io/gorm"
"time"
)
// SetComponentVerifyTicket 设置微信后台推送的ticket
func (app *App) SetComponentVerifyTicket(info *ResponseServeHttpVerifyTicket) string {
if info.ComponentVerifyTicket == "" {
return ""
}
app.Db.Create(&ComponentVerifyTicket{
AppId: info.AppId,
CreateTime: info.CreateTime,
InfoType: info.InfoType,
ComponentVerifyTicket: info.ComponentVerifyTicket,
ExpireTime: gotime.Current().AfterHour(12).Time,
})
return info.ComponentVerifyTicket
}
type ComponentVerifyTicket struct {
gorm.Model
AppId string `json:"app_id"` // 第三方平台 appid
CreateTime int64 `json:"create_time"` // 时间戳单位s
InfoType string `json:"info_type"` // 固定为:"component_verify_ticket"
ComponentVerifyTicket string `json:"component_verify_ticket"` // Ticket 内容
ExpireTime time.Time `json:"expire_time"` // 过期时间
}
func (m *ComponentVerifyTicket) TableName() string {
return "component_verify_ticket"
}

@ -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,131 @@
package wechatopen
import (
"encoding/base64"
"encoding/xml"
"errors"
"fmt"
"github.com/mitchellh/mapstructure"
"io/ioutil"
"net/http"
"strings"
)
// ResponseServeHttpVerifyTicket 验证票据推送
type ResponseServeHttpVerifyTicket struct {
XMLName xml.Name
AppId string `xml:"AppId" json:"AppId"` // 第三方平台 appid
CreateTime int64 `xml:"CreateTime" json:"CreateTime"` // 时间戳单位s
InfoType string `xml:"InfoType" json:"InfoType"` // 固定为:"component_verify_ticket"
ComponentVerifyTicket string `xml:"ComponentVerifyTicket" json:"ComponentVerifyTicket"` // Ticket 内容
}
type cipherRequestHttpBody struct {
AppId string `xml:"AppId" json:"AppId"` // 第三方平台 appid
Encrypt string `xml:"Encrypt" json:"Encrypt"` // 加密内容
}
// ServeHttpVerifyTicket 验证票据推送
func (app *App) ServeHttpVerifyTicket(r *http.Request) (resp *ResponseServeHttpVerifyTicket, err error) {
var (
query = r.URL.Query()
wantSignature string
haveSignature = query.Get("signature")
timestamp = query.Get("timestamp")
nonce = query.Get("nonce")
// post
haveMsgSignature = query.Get("msg_signature")
encryptType = query.Get("encrypt_type")
// handle vars
data []byte
requestHttpBody = &cipherRequestHttpBody{}
)
if haveSignature == "" {
err = errors.New("找不到签名参数")
return
}
if timestamp == "" {
return resp, errors.New("找不到时间戳参数")
}
if nonce == "" {
return resp, errors.New("未找到随机数参数")
}
wantSignature = Sign(app.MessageToken, timestamp, nonce)
if haveSignature != wantSignature {
return resp, errors.New("签名错误")
}
// 进入事件执行
if encryptType != "aes" {
err = errors.New("未知的加密类型: " + encryptType)
return
}
if haveMsgSignature == "" {
err = errors.New("找不到签名参数")
return
}
data, err = ioutil.ReadAll(r.Body)
if err != nil {
return resp, err
}
xmlDecode := XmlDecode(string(data))
if len(xmlDecode) <= 0 {
return resp, errors.New(fmt.Sprintf("Xml解码错误%s", xmlDecode))
}
err = mapstructure.Decode(xmlDecode, &requestHttpBody)
if err != nil {
return resp, errors.New(fmt.Sprintf("mapstructure 解码错误:%s", xmlDecode))
}
if requestHttpBody.Encrypt == "" {
return resp, errors.New(fmt.Sprintf("未找到加密数据:%s", requestHttpBody))
}
cipherData, err := base64.StdEncoding.DecodeString(requestHttpBody.Encrypt)
if err != nil {
return resp, errors.New(fmt.Sprintf("Encrypt 解码字符串错误:%v", err))
}
AesKey, err := base64.StdEncoding.DecodeString(app.MessageKey + "=")
if err != nil {
return resp, errors.New(fmt.Sprintf("MessageKey 解码字符串错误:%v", err))
}
msg, err := AesDecrypt(cipherData, AesKey)
if err != nil {
return resp, errors.New(fmt.Sprintf("AES解密错误%v", err))
}
str := string(msg)
left := strings.Index(str, "<xml>")
if left <= 0 {
return resp, errors.New(fmt.Sprintf("匹配不到<xml>%v", left))
}
right := strings.Index(str, "</xml>")
if right <= 0 {
return resp, errors.New(fmt.Sprintf("匹配不到</xml>%v", right))
}
msgStr := str[left:right]
if len(msgStr) == 0 {
return resp, errors.New(fmt.Sprintf("提取错误:%v", msgStr))
}
resp = &ResponseServeHttpVerifyTicket{}
err = xml.Unmarshal([]byte(msgStr+"</xml>"), resp)
if err != nil {
return resp, errors.New(fmt.Sprintf("解析错误:%v", err))
}
return resp, nil
}

@ -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,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,69 @@
package wechatopen
import (
"encoding/json"
"fmt"
"net/http"
)
type WxaCommitResponse struct {
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
}
type WxaCommitResult struct {
Result WxaCommitResponse // 结果
Body []byte // 内容
Err error // 错误
}
func NewWxaCommitResult(result WxaCommitResponse, body []byte, err error) *WxaCommitResult {
return &WxaCommitResult{Result: result, Body: body, Err: err}
}
// WxaCommit 上传小程序代码并生成体验版
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/code/commit.html
func (app *App) WxaCommit(notMustParams ...Params) *WxaCommitResult {
app.authorizerAccessToken = app.GetAuthorizerAccessToken()
// 参数
params := app.NewParamsWith(notMustParams...)
// 请求
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/wxa/commit?access_token=%s", app.authorizerAccessToken), params, http.MethodPost)
// 定义
var response WxaCommitResponse
err = json.Unmarshal(body, &response)
return NewWxaCommitResult(response, body, err)
}
// ErrcodeInfo 错误描述
func (resp *WxaCommitResult) ErrcodeInfo() string {
switch resp.Result.Errcode {
case 85013:
return "无效的自定义配置"
case 85014:
return "无效的模板编号"
case 85043:
return "模板错误"
case 85044:
return "代码包超过大小限制"
case 85045:
return "ext_json 有不存在的路径"
case 85046:
return "tabBar 中缺少 path"
case 85047:
return "pages 字段为空"
case 85048:
return "ext_json 解析失败"
case 80082:
return "没有权限使用该插件"
case 80067:
return "找不到使用的插件"
case 80066:
return "非法的插件版本"
case 9402202:
return "请勿频繁提交,待上一次操作完成后再提交"
case 9402203:
return `标准模板ext_json错误传了不合法的参数 如果是标准模板库的模板则ext_json支持的参数仅为{"extAppid":'', "ext": {}, "window": {}}`
}
return "系统繁忙"
}

@ -0,0 +1,46 @@
package wechatopen
import (
"encoding/json"
"fmt"
"net/http"
)
type WxaDeleteTemplateResponse struct {
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
}
type WxaDeleteTemplateResult struct {
Result WxaDeleteTemplateResponse // 结果
Body []byte // 内容
Err error // 错误
}
func NewWxaDeleteTemplateResult(result WxaDeleteTemplateResponse, body []byte, err error) *WxaDeleteTemplateResult {
return &WxaDeleteTemplateResult{Result: result, Body: body, Err: err}
}
// WxaDeleteTemplate 删除指定代码模板
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/ThirdParty/code_template/deletetemplate.html
func (app *App) WxaDeleteTemplate(templateId string) *WxaDeleteTemplateResult {
app.componentAccessToken = app.GetComponentAccessToken()
// 参数
params := NewParams()
params.Set("template_id", templateId)
// 请求
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/wxa/deletetemplate?access_token=%s", app.componentAccessToken), params, http.MethodPost)
// 定义
var response WxaDeleteTemplateResponse
err = json.Unmarshal(body, &response)
return NewWxaDeleteTemplateResult(response, body, err)
}
// ErrcodeInfo 错误描述
func (resp *WxaDeleteTemplateResult) ErrcodeInfo() string {
switch resp.Result.Errcode {
case 85064:
return "找不到模板请检查模板id是否输入正确"
}
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,45 @@
package wechatopen
import (
"encoding/json"
"fmt"
"net/http"
)
type WxaGetTemplateListResponse struct {
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
TemplateList []struct {
CreateTime int `json:"create_time"` // 被添加为模板的时间
UserVersion string `json:"user_version"` // 模板版本号,开发者自定义字段
UserDesc string `json:"user_desc"` // 模板描述,开发者自定义字段
TemplateId int64 `json:"template_id"` // 模板 id
TemplateType int `json:"template_type"` // 0对应普通模板1对应标准模板
SourceMiniprogramAppid string `json:"source_miniprogram_appid"` // 开发小程序的appid
SourceMiniprogram string `json:"source_miniprogram"` // 开发小程序的名称
Developer string `json:"developer"` // 开发者
CategoryList []interface{} `json:"category_list"`
} `json:"template_list"` // 模板信息列表
}
type WxaGetTemplateListResult struct {
Result WxaGetTemplateListResponse // 结果
Body []byte // 内容
Err error // 错误
}
func NewWxaGetTemplateListResult(result WxaGetTemplateListResponse, body []byte, err error) *WxaGetTemplateListResult {
return &WxaGetTemplateListResult{Result: result, Body: body, Err: err}
}
// WxaGetTemplateList 获取代码模板列表
// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/ThirdParty/code_template/gettemplatelist.html
func (app *App) WxaGetTemplateList() *WxaGetTemplateListResult {
app.componentAccessToken = app.GetComponentAccessToken()
// 请求
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/wxa/gettemplatelist?access_token=%s", app.componentAccessToken), map[string]interface{}{}, http.MethodGet)
// 定义
var response WxaGetTemplateListResponse
err = json.Unmarshal(body, &response)
return NewWxaGetTemplateListResult(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,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,43 @@
package wechatopen
import (
"encoding/xml"
"io"
"strings"
)
func XmlDecode(data string) map[string]string {
decoder := xml.NewDecoder(strings.NewReader(data))
result := make(map[string]string)
key := ""
for {
token, err := decoder.Token() //读取一个标签或者文本内容
if err == io.EOF {
return result
}
if err != nil {
return result
}
switch tp := token.(type) { //读取的TOKEN可以是以下三种类型StartElement起始标签EndElement结束标签CharData文本内容
case xml.StartElement:
se := xml.StartElement(tp) //强制类型转换
if se.Name.Local != "xml" {
key = se.Name.Local
}
if len(se.Attr) != 0 {
//读取标签属性
}
case xml.EndElement:
ee := xml.EndElement(tp)
if ee.Name.Local == "xml" {
return result
}
case xml.CharData: //文本数据,注意一个结束标签和另一个起始标签之间可能有空格
cd := xml.CharData(tp)
data := strings.TrimSpace(string(cd))
if len(data) != 0 {
result[key] = data
}
}
}
}
Loading…
Cancel
Save