Files
jx-print/services/print_server/app_server/wx_login.go
邹宗楠 8062dff677 1
2022-07-28 16:48:56 +08:00

172 lines
5.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package app_server
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"git.rosy.net.cn/baseapi/utils"
"git.rosy.net.cn/gopay-main/wechat"
"git.rosy.net.cn/jx-print/dao"
"git.rosy.net.cn/jx-print/globals"
"git.rosy.net.cn/jx-print/model"
wxLogin "git.rosy.net.cn/jx-print/model/app_model"
"git.rosy.net.cn/jx-print/services/api"
"github.com/gin-gonic/gin"
"reflect"
"time"
)
type UserLogin struct {
}
// WxLogin 授权登录
func (u *UserLogin) WxLogin(ctx *gin.Context, code, phone string) (*model.User, error) {
// 生成openId
openObj, err := api.WeixinMiniAPI.SNSRetrieveToken(code)
if err != nil {
return nil, err
}
// 检查用户是否存在
users, err := dao.GetUsers(globals.GetDB(), "", "", phone, "")
if err != nil {
return nil, err
}
if len(users) > 1 {
return nil, errors.New("数据异常,用户电话不唯一,联系管理员")
}
// 获取用户信息,是否注册
userInfo, err := api.WeixinMiniAPI.SNSGetUserInfo(openObj.AccessToken, openObj.OpenID)
if err != nil {
return nil, err
}
var userObj *model.User
timeNow := time.Now()
switch len(users) {
case 0:
// 用户不存在,创建并返回用户
userBase := &model.User{
CreatedAt: &timeNow,
UpdatedAt: &timeNow,
LastOperator: "系统新增",
DeletedAt: &utils.DefaultTimeValue,
UserID: utils.GetUUID(),
Password: "",
Name: userInfo.NickName,
Mobile: phone,
Email: "",
Avatar: userInfo.HeadImgURL,
Status: 1,
Type: 1,
Company: "",
CityCode: 0,
DistrictCode: 0,
Address: "",
IDCardNo: "",
Remark: "",
LastLoginAt: &timeNow,
LastLoginIP: ctx.ClientIP(),
LastLoginType: model.OrderOriginWxMini,
OpenId: userInfo.OpenID,
UnionId: userInfo.UnionID,
}
if err := dao.CreateUserWx(userBase); err != nil {
return nil, err
}
userObj = userBase
case 1:
// 用户使用电话号码登录,未使用微信登录
if users[0].OpenId == "" {
users[0].Name = userInfo.NickName
users[0].Avatar = userInfo.HeadImgURL
users[0].OpenId = userInfo.OpenID
users[0].UnionId = userInfo.UnionID
}
// 用户存在,判断用户
users[0].UpdatedAt = &timeNow
users[0].LastOperator = users[0].Name
users[0].LastLoginIP = ctx.ClientIP()
if err := dao.UpdateUserWx(users[0], []string{"updated_at", "last_operator", "last_login_ip", "name", "avatar", "open_id", "union_id"}); err != nil {
return nil, err
}
userObj = users[0]
}
return userObj, err
}
// GetUserPhoneNum 解密用户手机号
func (u *UserLogin) GetUserPhoneNum(param *wxLogin.WeChatPhoneNumberParam) (string, error) {
sessionInfo, err := api.WeixinMiniAPI.SNSCode2Session(param.Code)
if err != nil {
return "", err
}
weChatLogin := new(wechat.UserPhone)
if err := DecryptOpenDataToStruct(param.EncryptedData, param.IV, sessionInfo.SessionKey, weChatLogin); err != nil {
return "", err
}
return weChatLogin.PhoneNumber, nil
}
// DecryptOpenDataToStruct 解密开放数据到结构体
// encryptedData包括敏感数据在内的完整用户信息的加密数据小程序获取到
// iv加密算法的初始向量小程序获取到
// sessionKey会话密钥通过 gopay.Code2Session() 方法获取到
// beanPtr需要解析到的结构体指针操作完后声明的结构体会被赋值
// 文档https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html
func DecryptOpenDataToStruct(encryptedData, iv, sessionKey string, beanPtr interface{}) (err error) {
if encryptedData == "" || iv == "" || sessionKey == "" {
return errors.New("input params can not null")
}
var (
cipherText, aesKey, ivKey, plainText []byte
block cipher.Block
blockMode cipher.BlockMode
)
beanValue := reflect.ValueOf(beanPtr)
if beanValue.Kind() != reflect.Ptr {
return errors.New("传入beanPtr类型必须是以指针形式")
}
if beanValue.Elem().Kind() != reflect.Struct {
return errors.New("传入interface{}必须是结构体")
}
cipherText, _ = base64.StdEncoding.DecodeString(encryptedData)
aesKey, _ = base64.StdEncoding.DecodeString(sessionKey)
ivKey, _ = base64.StdEncoding.DecodeString(iv)
if len(cipherText)%len(aesKey) != 0 {
return errors.New("encryptedData is error")
}
if block, err = aes.NewCipher(aesKey); err != nil {
return fmt.Errorf("aes.NewCipher%w", err)
}
blockMode = cipher.NewCBCDecrypter(block, ivKey)
plainText = make([]byte, len(cipherText))
blockMode.CryptBlocks(plainText, cipherText)
if len(plainText) > 0 {
plainText = PKCS7UnPadding(plainText)
}
if err = json.Unmarshal(plainText, beanPtr); err != nil {
return fmt.Errorf("json.Marshal(%s)%w", string(plainText), err)
}
return
}
// 解密填充模式(去除补全码) PKCS7UnPadding
// 解密时需要在最后面去掉加密时添加的填充byte
func PKCS7UnPadding(origData []byte) (bs []byte) {
length := len(origData)
unPaddingNumber := int(origData[length-1]) // 找到Byte数组最后的填充byte 数字
if unPaddingNumber <= 16 {
bs = origData[:(length - unPaddingNumber)] // 只截取返回有效数字内的byte数组
} else {
bs = origData
}
return
}