update service

master
李光春 2 years ago
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,26 @@
package wechatmp
import (
"encoding/json"
"fmt"
)
// AuthGetAccessTokenResult 返回参数
type AuthGetAccessTokenResult struct {
AccessToken string `json:"access_token"` // 获取到的凭证
ExpiresIn int `json:"expires_in"` // 凭证有效时间单位秒。目前是7200秒之内的值
Errcode int `json:"errcode"` // 错误码
Errmsg string `json:"errmsg"` // 错误信息
}
func (app *App) AuthGetAccessToken() (result AuthGetAccessTokenResult, err error) {
// request
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", app.AppId, app.AppSecret), map[string]interface{}{}, "GET")
if err != nil {
return
}
if err = json.Unmarshal(body, &result); err != nil {
return
}
return
}

@ -0,0 +1,51 @@
package wechatmp
import (
"fmt"
)
type BusinessGetLiveInfoResult struct {
Errcode int `json:"errcode"` // // 错误码0代表成功1代表未创建直播间
Errmsg string `json:"errmsg"` // 错误信息
Total int `json:"total"`
RoomInfo []struct {
Name string `json:"name"` // 直播间名称
Roomid int `json:"roomid"` // 直播间ID
CoverImg string `json:"cover_img"`
ShareImg string `json:"share_img"`
LiveStatus int `json:"live_status"`
StartTime int `json:"start_time"`
EndTime int `json:"end_time"`
AnchorName string `json:"anchor_name"`
Goods []struct {
CoverImg string `json:"cover_img"`
Url string `json:"url"`
Name string `json:"name"`
Price int `json:"price"`
Price2 int `json:"price2"`
PriceType int `json:"price_type"`
GoodsId int `json:"goods_id"`
ThirdPartyAppid string `json:"third_party_appid"`
} `json:"goods"`
LiveType int `json:"live_type"`
CloseLike int `json:"close_like"`
CloseGoods int `json:"close_goods"`
CloseComment int `json:"close_comment"`
CloseKf int `json:"close_kf"`
CloseReplay int `json:"close_replay"`
IsFeedsPublic int `json:"is_feeds_public"`
CreaterOpenid string `json:"creater_openid"`
FeedsImg string `json:"feeds_img"`
} `json:"room_info"`
}
// BusinessGetLiveInfo 获取直播间列表
// 调用此接口获取直播间列表及直播间信息
// https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/liveplayer/studio-api.html
func (app *App) BusinessGetLiveInfo(notMustParams ...Params) (body []byte, err error) {
// 参数
params := app.NewParamsWith(notMustParams...)
// 请求
body, err = app.request(fmt.Sprintf("https://api.weixin.qq.com/wxa/business/getliveinfo?access_token=%s", app.AccessToken), params, "POST")
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,75 @@
package wechatmp
import (
"encoding/json"
"fmt"
"log"
"net/url"
)
// Oauth2 用户同意授权获取code
// https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#0
func (app *App) Oauth2(redirectUri, state string) string {
param := url.Values{}
param.Add("appid", app.AppId) // 公众号的唯一标识
param.Add("redirect_uri", redirectUri) // 授权后重定向的回调链接地址, 请使用 urlEncode 对链接进行处理
param.Add("response_type", "code") // 返回类型
param.Add("scope", "snsapi_userinfo") // 应用授权作用域
param.Add("state", state) // 重定向后会带上state参数开发者可以填写a-zA-Z0-9的参数值最多128字节
log.Println(fmt.Sprintf("https://open.weixin.qq.com/connect/oauth2/authorize?%s#wechat_redirect", param.Encode()))
return fmt.Sprintf("https://open.weixin.qq.com/connect/oauth2/authorize?%s#wechat_redirect", param.Encode())
}
// Oauth2AccessTokenResult 返回参数
type Oauth2AccessTokenResult struct {
AccessToken string `json:"access_token"` // 网页授权接口调用凭证,注意此access_token与基础支持的access_token不同
ExpiresIn int `json:"expires_in"` // access_token接口调用凭证超时时间单位
RefreshToken string `json:"refresh_token"` // 用户刷新access_token
Openid string `json:"openid"` // 用户唯一标识
Scope string `json:"scope"` // 用户授权的作用域,使用逗号(,)分隔
}
// Oauth2AccessToken 通过code换取网页授权access_token
// https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#0
func (app *App) Oauth2AccessToken(code string) (result Oauth2AccessTokenResult, err error) {
// 请求
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code", app.AppId, app.AppSecret, code), map[string]interface{}{}, "GET")
if err != nil {
return result, err
}
// 解析
err = json.Unmarshal(body, &result)
if err != nil {
return result, err
}
return result, err
}
// Oauth2UserinfoResult 返回参数
type Oauth2UserinfoResult struct {
Openid string `json:"openid"` // 用户的唯一标识
Nickname string `json:"nickname"` // 用户昵称
Sex int `json:"sex"` // 用户的性别值为1时是男性值为2时是女性值为0时是未知
Province string `json:"province"` // 用户个人资料填写的省份
City string `json:"city"` // 普通用户个人资料填写的城市
Country string `json:"country"` // 国家如中国为CN
Headimgurl string `json:"headimgurl"` // 用户头像最后一个数值代表正方形头像大小有0、46、64、96、132数值可选0代表640*640正方形头像用户没有头像时该项为空。若用户更换头像原有头像URL将失效。
Privilege []string `json:"privilege"` // 用户特权信息json 数组如微信沃卡用户为chinaunicom
Unionid string `json:"unionid,omitempty"` // 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。
}
// Oauth2Userinfo 拉取用户信息(需scope为 snsapi_userinfo)
// https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#0
func (app *App) Oauth2Userinfo(accessToken, openid string) (result Oauth2UserinfoResult, err error) {
// 请求
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN", accessToken, openid), map[string]interface{}{}, "GET")
if err != nil {
return result, err
}
// 解析
err = json.Unmarshal(body, &result)
if err != nil {
return result, err
}
return result, err
}

@ -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&timestamp=%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,43 @@
package wechatmp
import (
"encoding/json"
"fmt"
)
// SubscribeMessageSend 入参
type SubscribeMessageSend struct {
Touser string `json:"touser"` // 接收者(用户)的 openid
TemplateId string `json:"template_id"` // 所需下发的订阅模板id
Page string `json:"page,omitempty"` // 点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,示例index?foo=bar。该字段不填则模板无跳转。
Data map[string]interface{} `json:"data"` // 模板内容,格式形如 { "key1": { "value": any }, "key2": { "value": any } }
MiniprogramState string `json:"miniprogram_state,omitempty"` // 跳转小程序类型developer为开发版trial为体验版formal为正式版默认为正式版
Lang string `json:"lang,omitempty"` // 进入小程序查看”的语言类型支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文)默认为zh_CN
}
// SubscribeMessageSendResult 返回参数
type SubscribeMessageSendResult struct {
Errcode int // 错误码
Errmsg string // 错误信息
}
func (app *App) SubscribeMessageSend(param SubscribeMessageSend) (result SubscribeMessageSendResult, err error) {
// api params
params := map[string]interface{}{}
b, _ := json.Marshal(&param)
var m map[string]interface{}
_ = json.Unmarshal(b, &m)
for k, v := range m {
params[k] = v
}
// request
body, err := app.request(fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=%s", app.AccessToken), params, "POST")
if err != nil {
return
}
if err = json.Unmarshal(body, &result); err != nil {
return
}
return
}

@ -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…
Cancel
Save