package app_server import ( "crypto/aes" "crypto/cipher" "encoding/base64" "encoding/json" "errors" "fmt" "git.rosy.net.cn/baseapi/utils" "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" "io/ioutil" "net/http" "net/url" "reflect" "strconv" "strings" "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(wxLogin.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 } // Jxc4UserToken 全局变量,缓存菜市管理系统token var Jxc4UserToken = map[string]string{"token": "", "expirationTime": ""} // GetJxc4Token 获取京西菜市token func (u *UserLogin) GetJxc4Token() (string, error) { if Jxc4UserToken["token"] != "" && Jxc4UserToken["expirationTime"] != "" { timeBegin, _ := strconv.ParseInt(Jxc4UserToken["expirationTime"], 10, 64) timeNow := time.Now().Unix() if timeBegin-timeNow > 0 { // 没过期 return Jxc4UserToken["token"], nil } } //TODO 参数为登录菜市管理系统的管理员用户账号:目前账号为 15141938808 密码111111 params := map[string]interface{}{"authType": "localpass", "authIDType": "mobile", "authID": "15141938808", "authSecret": "96e79218965eb72c92a549dd5a330112"} retVal := make(url.Values) for k, v := range params { retVal.Set(k, fmt.Sprint(v)) } strings.NewReader(utils.Map2URLValues(params).Encode()) resp, err := http.Post("https://www.jxc4.com/v2/auth2/Login", "application/x-www-form-urlencoded", strings.NewReader(retVal.Encode())) if err != nil { return "", err } defer resp.Body.Close() bodyResp, err := ioutil.ReadAll(resp.Body) if err != nil { return "", err } jxc4User := &model.AutoGenerated{} if err := json.Unmarshal(bodyResp, jxc4User); err != nil { return "", err } Jxc4UserToken["token"] = jxc4User.Data.Token Jxc4UserToken["expirationTime"] = strconv.FormatInt(time.Now().Unix(), 64) return jxc4User.Data.Token, nil }