parent
08869750de
commit
1c23b62743
@ -0,0 +1,45 @@
|
|||||||
|
package wechatmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// App 微信小程序服务
|
||||||
|
type App struct {
|
||||||
|
AppId string // 小程序唯一凭证,即 AppID
|
||||||
|
AppSecret string // 小程序唯一凭证密钥,即 AppSecret
|
||||||
|
AccessToken string // 接口调用凭证
|
||||||
|
JsapiTicket string // 签名凭证
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) request(url string, params map[string]interface{}, method string) (resp []byte, err error) {
|
||||||
|
// 请求参数
|
||||||
|
marshal, _ := json.Marshal(params)
|
||||||
|
var req *http.Request
|
||||||
|
req, err = http.NewRequest(method, url, bytes.NewReader(marshal))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient := &http.Client{}
|
||||||
|
var response *http.Response
|
||||||
|
response, err = httpClient.Do(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理成功
|
||||||
|
defer response.Body.Close()
|
||||||
|
resp, err = ioutil.ReadAll(response.Body)
|
||||||
|
|
||||||
|
// 检查请求错误
|
||||||
|
if response.StatusCode == 200 {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package wechatmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthCode2Session 请求参数
|
||||||
|
type AuthCode2Session struct {
|
||||||
|
JsCode string `json:"js_code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthCode2SessionResult 返回参数
|
||||||
|
type AuthCode2SessionResult struct {
|
||||||
|
OpenId string `json:"openid"` // 用户唯一标识
|
||||||
|
SessionKey string `json:"session_key"` // 会话密钥
|
||||||
|
Unionid string `json:"unionid"` // 用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台帐号下会返回
|
||||||
|
Errcode string `json:"errcode"` // 错误码
|
||||||
|
Errmsg string `json:"errmsg"` // 错误信息
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) AuthCode2Session(param AuthCode2Session) (result AuthCode2SessionResult, err error) {
|
||||||
|
// request
|
||||||
|
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", app.AppId, app.AppSecret, param.JsCode), map[string]interface{}{}, "GET")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(body, &result); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package wechatmp
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type GetCallBackIpResult struct {
|
||||||
|
IpList []string `json:"ip_list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCallBackIp 获取微信callback IP地址
|
||||||
|
// callback IP即微信调用开发者服务器所使用的出口IP。
|
||||||
|
// https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_the_WeChat_server_IP_address.html#2.%20%E8%8E%B7%E5%8F%96%E5%BE%AE%E4%BF%A1callback%20IP%E5%9C%B0%E5%9D%80
|
||||||
|
func (app *App) GetCallBackIp() (body []byte, err error) {
|
||||||
|
// 请求
|
||||||
|
body, err = app.request(fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token=%s", app.AccessToken), map[string]interface{}{}, "GET")
|
||||||
|
return
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package wechatmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetTicketResult 返回参数
|
||||||
|
type GetTicketResult struct {
|
||||||
|
Errcode int `json:"errcode"` // 错误码
|
||||||
|
Errmsg string `json:"errmsg"` // 错误信息
|
||||||
|
Ticket string `json:"ticket"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) GetTicket(accessToken, Type string) (result GetTicketResult, err error) {
|
||||||
|
// request
|
||||||
|
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=%s", accessToken, Type), map[string]interface{}{}, "GET")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(body, &result); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package wechatmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (app *App) MessageTemplateSend(notMustParams ...Params) (body []byte, err error) {
|
||||||
|
// 参数
|
||||||
|
params := app.NewParamsWith(notMustParams...)
|
||||||
|
// 请求
|
||||||
|
body, err = app.request(fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s", app.AccessToken), params, "POST")
|
||||||
|
return
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package wechatmp
|
||||||
|
|
||||||
|
// 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,30 @@
|
|||||||
|
package wechatmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"fmt"
|
||||||
|
"github.com/dtapps/go-library/utils/gorandom"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ShareResult struct {
|
||||||
|
AppId string `json:"app_id"`
|
||||||
|
NonceStr string `json:"nonce_str"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
RawString string `json:"raw_string"`
|
||||||
|
Signature string `json:"signature"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) Share(url string) (result ShareResult) {
|
||||||
|
result.AppId = app.AppId
|
||||||
|
result.NonceStr = gorandom.Alphanumeric(32)
|
||||||
|
result.Timestamp = time.Now().Unix()
|
||||||
|
result.Url = url
|
||||||
|
result.RawString = fmt.Sprintf("jsapi_ticket=%v&noncestr=%v×tamp=%v&url=%v", app.JsapiTicket, result.NonceStr, result.Timestamp, result.Url)
|
||||||
|
t := sha1.New()
|
||||||
|
io.WriteString(t, result.RawString)
|
||||||
|
result.Signature = fmt.Sprintf("%x", t.Sum(nil))
|
||||||
|
return result
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package wechatmp
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
func (app *App) 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,66 @@
|
|||||||
|
package wechatmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserInfo 请求参数
|
||||||
|
type UserInfo struct {
|
||||||
|
SessionKey string `json:"session_key"`
|
||||||
|
EncryptedData string `json:"encrypted_data"`
|
||||||
|
Iv string `json:"iv"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserInfoResult 返回参数
|
||||||
|
type UserInfoResult 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserInfo 解密用户信息
|
||||||
|
func (app *App) UserInfo(param UserInfo) (result UserInfoResult, err error) {
|
||||||
|
aesKey, err := base64.StdEncoding.DecodeString(param.SessionKey)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
cipherText, err := base64.StdEncoding.DecodeString(param.EncryptedData)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
ivBytes, err := base64.StdEncoding.DecodeString(param.Iv)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
block, err := aes.NewCipher(aesKey)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
mode := cipher.NewCBCDecrypter(block, ivBytes)
|
||||||
|
mode.CryptBlocks(cipherText, cipherText)
|
||||||
|
cipherText, err = app.pkcs7Unpaid(cipherText, block.BlockSize())
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(cipherText, &result)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
if result.Watermark.AppID != app.AppId {
|
||||||
|
return result, errors.New("app id not match")
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package wechatmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserPhone 请求参数
|
||||||
|
type UserPhone struct {
|
||||||
|
SessionKey string `json:"session_key"`
|
||||||
|
EncryptedData string `json:"encrypted_data"`
|
||||||
|
Iv string `json:"iv"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserPhoneResult 返回参数
|
||||||
|
type UserPhoneResult struct {
|
||||||
|
PhoneNumber string `json:"phoneNumber"` // 用户绑定的手机号(国外手机号会有区号)
|
||||||
|
PurePhoneNumber string `json:"purePhoneNumber"` // 没有区号的手机号
|
||||||
|
CountryCode string `json:"countryCode"` // 区号
|
||||||
|
Watermark struct {
|
||||||
|
AppID string `json:"appid"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
} `json:"watermark"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserPhone 解密手机号信息
|
||||||
|
func (app *App) UserPhone(param UserPhone) (result UserPhoneResult, err error) {
|
||||||
|
aesKey, err := base64.StdEncoding.DecodeString(param.SessionKey)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
cipherText, err := base64.StdEncoding.DecodeString(param.EncryptedData)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
ivBytes, err := base64.StdEncoding.DecodeString(param.Iv)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
block, err := aes.NewCipher(aesKey)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
mode := cipher.NewCBCDecrypter(block, ivBytes)
|
||||||
|
mode.CryptBlocks(cipherText, cipherText)
|
||||||
|
cipherText, err = app.pkcs7Unpaid(cipherText, block.BlockSize())
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(cipherText, &result)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
if result.Watermark.AppID != app.AppId {
|
||||||
|
return result, errors.New("app id not match")
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
Loading…
Reference in new issue