From df52328b0bb5f22471786b11d7cf9ac23bedf419 Mon Sep 17 00:00:00 2001 From: kanade Date: Fri, 1 Apr 2022 17:14:16 +0800 Subject: [PATCH] init --- .gitignore | 1 + README.md | 44 +++++++++++++++ go.mod | 3 ++ open.go | 80 ++++++++++++++++++++++++++++ request.go | 37 +++++++++++++ wxapp.go | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 319 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 go.mod create mode 100644 open.go create mode 100644 request.go create mode 100644 wxapp.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..723ef36 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c56545 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# 微信登录工具 + +### 公众号 +```go +package main + +import ( + "git.oa00.com/go/wx" +) + +func main() { + // 初始化配置 + wx.InitWxopen("appid", "secret") + + // 获取accessToken和openId + wx.Wxopen.GetAccessInfo("code") + + // 获取用户信息 + wx.Wxopen.GetUserInfo("accessToken", "openId") +} +``` + +### 小程序 +```go +package main + +import ( + "git.oa00.com/go/wx" +) + +func main() { + // 初始化配置 + wx.InitWxapp("appid","secret") + + // 获取登录信息 + wx.Wxapp.GetLoginInfo("code") + + // 获取用户信息 + wx.Wxapp.GetUserInfo("sessionKey", "encryptedData", "iv") + + // 获取手机号 + wx.Wxapp.GetPhone("sessionKey", "encryptedData", "iv") +} +``` \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f6c6404 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.oa00.com/go/wx + +go 1.17 diff --git a/open.go b/open.go new file mode 100644 index 0000000..0364f2e --- /dev/null +++ b/open.go @@ -0,0 +1,80 @@ +package wx + +import ( + "encoding/json" + "errors" + "fmt" +) + +var Wxopen = &wxopen{} + +type wxopen struct { + appid string + secret string +} + +// InitWxopen @Title 初始化开放平台 +func InitWxopen(appid, secret string) { + Wxopen.appid = appid + Wxopen.secret = secret +} + +type AccessInfo struct { + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + Openid string `json:"openid"` + Scope string `json:"scope"` + Unionid string `json:"unionid"` +} +type errInfo struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` +} + +type resAccess struct { + errInfo + AccessInfo +} + +// GetAccessInfo @Title 获取微信登录验证结构体 +func (w *wxopen) GetAccessInfo(code string) (result AccessInfo, err error) { + res, err := request(get, fmt.Sprintf("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%v&secret=%v&code=%v&grant_type=authorization_code", w.appid, w.secret, code), "") + if err != nil { + return + } + var resAcc resAccess + if err = json.Unmarshal(res, &resAcc); err != nil { + return + } + if resAcc.Errcode != 0 { + return result, errors.New(resAcc.Errmsg) + } + result = resAcc.AccessInfo + return +} + +type userInfo struct { + City string `json:"city"` + Country string `json:"country"` + Headimgurl string `json:"headimgurl"` + Nickname string `json:"nickname"` + Openid string `json:"openid"` + Privilege []string `json:"privilege"` + Province string `json:"province"` + Sex uint `json:"sex"` + Unionid string `json:"unionid"` +} + +// GetUserInfo @Title 获取微信登录会员信息 +func (w *wxopen) GetUserInfo(accessToken, openId string) (*userInfo, error) { + res, err := request(get, fmt.Sprintf("https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN", accessToken, openId), "") + if err != nil { + return nil, err + } + result := userInfo{} + if err := json.Unmarshal(res, &result); err != nil { + return nil, err + } + return &result, nil +} diff --git a/request.go b/request.go new file mode 100644 index 0000000..7f83c39 --- /dev/null +++ b/request.go @@ -0,0 +1,37 @@ +package wx + +import ( + "io" + "net/http" + "strings" +) + +const ( + post = "POST" + get = "GET" +) + +var client = &http.Client{} + +// @Title 请求 +func request(method, url, data string, headers ...map[string]string) ([]byte, error) { + reqest, err := http.NewRequest(method, url, strings.NewReader(data)) + if err != nil { + return nil, err + } + if len(headers) > 0 { + for key, value := range headers[0] { + reqest.Header.Add(key, value) + } + } + response, err := client.Do(reqest) + if err != nil { + return nil, err + } + defer response.Body.Close() + result, err := io.ReadAll(response.Body) + if err != nil { + return nil, err + } + return result, nil +} diff --git a/wxapp.go b/wxapp.go new file mode 100644 index 0000000..1949bdd --- /dev/null +++ b/wxapp.go @@ -0,0 +1,154 @@ +package wx + +import ( + "crypto/aes" + "crypto/cipher" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "log" +) + +var Wxapp = &wxapp{} + +type wxapp struct { + appid string + secret string +} + +// InitWxapp @Title 初始化wxapp +func InitWxapp(appid, secret string) { + Wxapp.appid = appid + Wxapp.secret = secret +} + +type resLogin struct { + LoginInfo + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` +} +type LoginInfo struct { + SessionKey string `json:"session_key"` + Openid string `json:"openid"` + Unionid string `json:"unionid"` +} + +// GetLoginInfo @Title 获取登录信息 +func (w *wxapp) GetLoginInfo(code string) (info LoginInfo, err error) { + bytes, err := request("get", fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", + w.appid, w.secret, code), "") + if err != nil { + return + } + log.Println(string(bytes)) + res := resLogin{} + if err = json.Unmarshal(bytes, &res); err != nil { + return + } + if res.Errcode > 0 { + return info, errors.New(res.Errmsg) + } + return res.LoginInfo, nil +} + +type UserInfo struct { + OpenID string `json:"openId"` + UnionID string `json:"unionId"` + NickName string `json:"nickName"` + Gender int `json:"gender"` + City string `json:"city"` + Province string `json:"province"` + Country string `json:"country"` + AvatarURL string `json:"avatarUrl"` + Language string `json:"language"` + Watermark struct { + Timestamp int64 `json:"timestamp"` + AppID string `json:"appid"` + } `json:"watermark"` +} + +// GetUserInfo @Title 获取用户信息 +func (w *wxapp) GetUserInfo(sessionKey, encryptedData, iv string) (userInfo UserInfo, err error) { + decrypt, err := w.Decrypt(sessionKey, encryptedData, iv) + if err != nil { + return + } + err = json.Unmarshal(decrypt, &userInfo) + if err != nil { + return + } + if userInfo.Watermark.AppID != w.appid { + return userInfo, errors.New("app id not match") + } + return +} + +type UserPhone struct { + PhoneNumber string `json:"phoneNumber"` + PurePhoneNumber string `json:"purePhoneNumber"` + CountryCode string `json:"countryCode"` + Watermark struct { + Timestamp int `json:"timestamp"` + Appid string `json:"appid"` + } `json:"watermark"` +} + +// GetPhone @Title 获取用户手机号 +func (w *wxapp) GetPhone(sessionKey, encryptedData, iv string) (userPhone UserPhone, err error) { + decrypt, err := w.Decrypt(sessionKey, encryptedData, iv) + if err != nil { + return + } + if err = json.Unmarshal(decrypt, &userPhone); err != nil { + return + } + return +} + +// Decrypt @Title 解密数据 +func (w *wxapp) Decrypt(sessionKey, encryptedData, iv string) (decrypt []byte, err error) { + aesKey, err := base64.StdEncoding.DecodeString(sessionKey) + if err != nil { + return + } + cipherText, err := base64.StdEncoding.DecodeString(encryptedData) + if err != nil { + return + } + ivBytes, err := base64.StdEncoding.DecodeString(iv) + if err != nil { + return + } + block, err := aes.NewCipher(aesKey) + if err != nil { + return + } + mode := cipher.NewCBCDecrypter(block, ivBytes) + mode.CryptBlocks(cipherText, cipherText) + decrypt, err = w.pkcs7Unpad(cipherText, block.BlockSize()) + if err != nil { + return + } + return +} + +func (w *wxapp) pkcs7Unpad(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 +}