package weixin import ( "encoding/base64" "errors" "fmt" "time" "git.rosy.net.cn/baseapi/platformapi/weixinapi" "git.rosy.net.cn/baseapi/utils" weixin2 "git.rosy.net.cn/jx-callback/business/auth2/authprovider/weixin" "git.rosy.net.cn/jx-callback/business/jxcallback/auth" "git.rosy.net.cn/jx-callback/business/jxcallback/auth/mobile" "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" "git.rosy.net.cn/jx-callback/business/model/dao" "git.rosy.net.cn/jx-callback/globals" "git.rosy.net.cn/jx-callback/globals/api" ) const ( LoginType = "weixinsns" LoginTypeMiniProgram = "weixinmini" DefTempPasswordDuration = 20 * time.Minute // 登录时间限制在5分钟内 ) const ( CacheKeySeparator = "/" MiniVerifyCodePrefix = "MiniVerifyCode" SessionKeyPrefix = "SessionKey" ) var ( StrStateIsWrong = "state:%s状态不对" ) var ( auther *Auther AutherMini *AutherMiniProgram ) var ( ErrExceptionalLogin = errors.New("登录异常,超时,请重走绑定流程") ) type Auther struct { } type AutherMiniProgram struct { } type UserInfoExt struct { weixinapi.SNSUserInfo TempPassword string `json:"tempPassword"` // 一段时间有效的登录密码 LoginInfo *auth.LoginInfo `json:"loginInfo"` } func init() { auther = new(Auther) auth.RegisterAuther(LoginType, auther) AutherMini = new(AutherMiniProgram) auth.RegisterAuther(LoginTypeMiniProgram, AutherMini) } func cacheSNSInfo(wxUserinfo *weixinapi.SNSUserInfo, password string, duration time.Duration) { api.Cacher.Set(wxUserinfo.OpenID, password, duration) api.Cacher.Set(wxUserinfo.OpenID+".sns", wxUserinfo, duration) } func getSNSInfoFromCache(openID string) (wxUserinfo *weixinapi.SNSUserInfo, password string) { password, _ = api.Cacher.Get(openID).(string) wxUserinfo = new(weixinapi.SNSUserInfo) if err := api.Cacher.GetAs(openID+".sns", wxUserinfo); err != nil { wxUserinfo = nil } return wxUserinfo, password } func GetWeiXinUserInfo(code string, state string) (userInfo *UserInfoExt, err error) { globals.SugarLogger.Debugf("GetUserInfo code:%s", code) if state == "" { token, err2 := api.WeixinAPI.SNSRetrieveToken(code) if err = err2; err == nil { wxUserinfo, err2 := api.WeixinAPI.SNSGetUserInfo(token.AccessToken, token.OpenID) if err = err2; err == nil { userInfo = &UserInfoExt{ SNSUserInfo: *wxUserinfo, TempPassword: utils.GetUUID(), } globals.SugarLogger.Debugf("GetUserInfo code:%s, userInfo:%s", code, utils.Format4Output(userInfo, true)) cacheSNSInfo(wxUserinfo, userInfo.TempPassword, DefTempPasswordDuration) user, err2 := dao.GetWeiXinUserByIDs(dao.GetDB(), "", wxUserinfo.UnionID, wxUserinfo.OpenID, "") if err = err2; err == nil { userInfo.LoginInfo = auth.CreateLoginInfo(user.Tel, mobile.LoginType) } else if !dao.IsNoRowsError(err) { // 非用户不存在错误,报错 return nil, err } return userInfo, nil } } } else { err = fmt.Errorf(StrStateIsWrong, state) } return nil, err } // 此函数需要调整 func (a *Auther) Login(openid, password string) (userID, LoginType string, err error) { globals.SugarLogger.Debugf("weixinsns Login openid:%s, password:%s", openid, password) _, cachedPwd := getSNSInfoFromCache(openid) if cachedPwd != "" && password == cachedPwd { api.Cacher.Del(openid) return "", "", nil } return "", "", ErrExceptionalLogin } func (a *Auther) Logout(loginInfo *auth.LoginInfo) error { return nil } func BindMobile(token, mobileNum, code, nickname string) (err error) { globals.SugarLogger.Debugf("BindMobile token:%s, mobileNum:%s, code:%s, nickname:%s", token, mobileNum, code, nickname) loginInfo := new(auth.LoginInfo) if err = api.Cacher.GetAs(token, loginInfo); err == nil { if err = mobile.VerifyCode(mobileNum, code); err == nil { wxUserinfo, _ := getSNSInfoFromCache(loginInfo.ID) if wxUserinfo == nil { return fmt.Errorf("绑定超时,请重新绑定") } if nickname == "" { nickname = wxUserinfo.NickName } err = auth.ConvertErr2NoUser(dao.UpdateWeiXinUser(dao.GetDB(), mobileNum, nickname, wxUserinfo.UnionID, wxUserinfo.OpenID, ""), mobileNum) } } jxutils.HandleUserWXRemark(nil, mobileNum) return err } // 绑定手机加登录 func BindMobile2(openid, secret, mobileNum, verifyCode, nickname string) (loginInfo *auth.LoginInfo, err error) { globals.SugarLogger.Debugf("BindMobile2 openid:%s, secret:%s, mobileNum:%s, verifyCode:%s, nickname:%s", openid, secret, mobileNum, verifyCode, nickname) err = ErrExceptionalLogin if value := api.Cacher.Get(openid); value != nil { wxUserinfo, cachedSecret := getSNSInfoFromCache(openid) if wxUserinfo == nil { return nil, fmt.Errorf("绑定超时,请重新绑定") } if secret == cachedSecret { if err = mobile.VerifyCode(mobileNum, verifyCode); err == nil { api.Cacher.Del(openid) err = nil if nickname == "" { nickname = wxUserinfo.NickName } if err = dao.UpdateWeiXinUser(dao.GetDB(), mobileNum, nickname, wxUserinfo.UnionID, wxUserinfo.OpenID, ""); err == nil { loginInfo = auth.CreateLoginInfo(mobileNum, mobile.LoginType) } else { err = auth.ConvertErr2NoUser(err, mobileNum) } } } } jxutils.HandleUserWXRemark(nil, mobileNum) return loginInfo, err } // 此函数已废弃 // 对于小程序来说, // 1,用户必须先在后台创建(手机号标识) // 2,用户必须先绑定微信 // 先以短信方式登录: // SendMobileVerifyCode // Login use type mobile // MiniBindWeiXin // 3,用户以CODE来登录(Login use type weixinmini) // Login func (a *AutherMiniProgram) BindWeiXin(ctx *jxcontext.Context, code, nickName string) (err error) { globals.SugarLogger.Debugf("AutherMiniProgram BindWeiXin code:%s, nickName:%s", code, nickName) loginInfo := ctx.GetLoginInfo() if loginInfo == nil || loginInfo.GetAuthType() != mobile.LoginType { return fmt.Errorf("调用AutherMiniProgram BindWeiXin时,必须以手机验证方式登录") } sessionInfo, err := weixin2.ProxySNSCode2Session(code) if err != nil { return err } err = dao.UpdateWeiXinUser(dao.GetDB(), loginInfo.GetAuthID(), nickName, sessionInfo.UnionID, "", sessionInfo.OpenID) return auth.ConvertErr2NoUser(err, "") } // 绑定小程序 func (a *AutherMiniProgram) BindMiniProgram(ctx *jxcontext.Context, code string) (err error) { globals.SugarLogger.Debugf("BindMiniProgram code:%s", code) if ctx.GetLoginType() != mobile.LoginType { return errors.New("登录方式应该为手机") } sessionInfo, err := weixin2.ProxySNSCode2Session(code) if err != nil { return err } db := dao.GetDB() user, err := dao.GetWeiXinUserByIDs(db, ctx.GetLoginID(), "", "", "") if err != nil { return err } // if user.OpenIDUnion != sessionInfo.UnionID { // return errors.New("绑定用户不匹配") // } err = auth.ConvertErr2NoUser(dao.UpdateWeiXinUser(db, user.Tel, "", sessionInfo.UnionID, "", sessionInfo.OpenID), user.Tel) return err } func (a *AutherMiniProgram) Login(mobileNum, code string) (userID, LoginType string, err error) { globals.SugarLogger.Debugf("AutherMiniProgram Login mobileNum:%s, code:%s", mobileNum, code) sessionInfo, err := weixin2.ProxySNSCode2Session(code) if err != nil { return "", "", err } globals.SugarLogger.Debugf("AutherMiniProgram Login code:%s, unionID:%s, openID:%s", code, sessionInfo.UnionID, sessionInfo.OpenID) db := dao.GetDB() user, err := dao.GetWeiXinUserByIDs(db, "", sessionInfo.UnionID, "", sessionInfo.OpenID) if err != nil { return "", "", auth.ConvertErr2NoUser(err, mobileNum) } if user.OpenIDMini != sessionInfo.OpenID { user.OpenIDMini = sessionInfo.OpenID dao.UpdateEntity(db, user, "OpenIDMini") } globals.SugarLogger.Debugf("AutherMiniProgram Login user.Tel:%s, code:%s, openID:%s", user.Tel, code, sessionInfo.OpenID) if mobileNum != user.Tel { } api.Cacher.Set(composeSessionKeyCacheKey(user.Tel), sessionInfo.SessionKey, auth.DefTokenDuration) return user.Tel, mobile.LoginType, err } func (a *AutherMiniProgram) Logout(loginInfo *auth.LoginInfo) error { globals.SugarLogger.Debugf("AutherMiniProgram Logout openid:%s", utils.Format4Output(loginInfo, false)) return api.Cacher.Del(composeSessionKeyCacheKey(loginInfo.GetAuthID())) } func (a *AutherMiniProgram) DecryptData(ctx *jxcontext.Context, encryptedData, iv string) (decryptedDataBase64 string, err error) { globals.SugarLogger.Debugf("AutherMiniProgram DecryptData encryptedData:%s, iv:%s", encryptedData, iv) var sessionKey string if err = api.Cacher.GetAs(composeSessionKeyCacheKey(ctx.GetLoginInfo().GetAuthID()), &sessionKey); err != nil { return "", err } decryptedData, err := weixin2.ProxySNSDecodeMiniProgramData(encryptedData, sessionKey, iv) if err != nil { return "", err } return base64.StdEncoding.EncodeToString(decryptedData), nil } func composeMiniVerifiyCacheKey(key string) string { return MiniVerifyCodePrefix + CacheKeySeparator + key } func composeSessionKeyCacheKey(key string) string { return SessionKeyPrefix + CacheKeySeparator + key }