package auth import ( "crypto/rand" "encoding/base64" "errors" "eta/eta_mini_ht_api/common/component/cache" logger "eta/eta_mini_ht_api/common/component/log" "eta/eta_mini_ht_api/common/component/wechat" "eta/eta_mini_ht_api/common/exception" authUtils "eta/eta_mini_ht_api/common/utils/auth" "eta/eta_mini_ht_api/common/utils/jwt" "eta/eta_mini_ht_api/common/utils/redis" "eta/eta_mini_ht_api/common/utils/sms" stringUtils "eta/eta_mini_ht_api/common/utils/string" smsService "eta/eta_mini_ht_api/domian/sms" userService "eta/eta_mini_ht_api/domian/user" userDao "eta/eta_mini_ht_api/models/user" "fmt" "gorm.io/gorm" "strings" ) var ( smsSender sms.SMSClient wechatClient *wechat.Client redisCache *cache.RedisCache ) const ( emptyPhoneNum = "-" ) func message() sms.SMSClient { if smsSender == nil { smsSender = sms.GetInstance() } return smsSender } func wx() *wechat.Client { if wechatClient == nil { wechatClient = wechat.GetInstance() } return wechatClient } func SendSMSCode(mobile string) (err error) { //code := rd().GetString(redis.GenerateSmsKey(mobile)) //if stringUtils.IsEmptyOrNil(code) { // code, err = authUtils.GenerateCode(6) // if err != nil { // logger.Warn("生成验证码失败:%v", err) // return exception.New(exception.SMSCodeGenerateFailed) // } // codeDTO := smsService.CodeDTO{ // Mobile: mobile, // Code: code, // ExpireMinute: message().GetExpireMinute(), // } // //消息domain层 // var smid int // smid, err = smsService.SendSMSCode(codeDTO) // if err != nil { // logger.Error("发送短信失败:%v", err) // err = exception.New(exception.SendingSMSFailed) // } // logger.Debug("验证码:%v", code) // _, err = message().SendSms(mobile, code, smid) // if err != nil { // logger.Error("发送短信失败:%v", err) // err = exception.New(exception.SendingSMSFailed) // } // return //} //return exception.New(exception.SMSCodeAlreadySent) code := rd().GetString(redis.GenerateSmsKey(mobile)) if stringUtils.IsEmptyOrNil(code) { logger.Info("更新验证码 手机号[%v],旧验证码[%v]", mobile, code) } code, err = authUtils.GenerateCode(6) logger.Info("更新验证码 手机号[%v],新验证码[%v]", mobile, code) if err != nil { logger.Warn("生成验证码失败:%v", err) return exception.New(exception.SMSCodeGenerateFailed) } codeDTO := smsService.CodeDTO{ Mobile: mobile, Code: code, ExpireMinute: message().GetExpireMinute(), } //消息domain层 var smid int smid, err = smsService.SendSMSCode(codeDTO) if err != nil { logger.Error("发送短信失败:%v", err) err = exception.New(exception.SendingSMSFailed) } logger.Debug("验证码:%v", code) _, err = message().SendSms(mobile, code, smid) if err != nil { logger.Error("发送短信失败:%v", err) err = exception.New(exception.SendingSMSFailed) } return } func CheckUser(mobile string, code string) (err error) { smsCode := rd().GetString(redis.GenerateSmsKey(mobile)) if stringUtils.IsEmptyOrNil(smsCode) { logger.Warn("验证码已过期:%v", code) codeDTO := smsService.CodeDTO{ Mobile: mobile, Code: code, } _ = smsService.TryExpireCode(codeDTO) err = exception.New(exception.SMSCodeExpired) return } if smsCode != code { err = exception.New(exception.SMSCodeError) return } return nil } type LoginDTO struct { VerifyCode string Code string Mobile string } func Logout(userId int, openId string) (err error) { //设置用户为登录状态 err = userService.UserLogout(userId) if err != nil { logger.Error("用户退出登录失败:%v", err) err = exception.New(exception.LogoutFailed) return } _ = userService.UserLogoutFlow(userId) err = rd().Delete(redis.GenerateTokenKey(openId)) if err != nil { err = rd().SetString(redis.GenerateTokenKey(openId), "", 1) } if err != nil { logger.Error("用户退出登录失败:%v", err) err = exception.New(exception.LogoutFailed) return } return } func Login(userId int, openId, mobile, verifyCode string) (token string, err error) { //设置用户为登录状态 err = userService.UserLogin(userId) if err != nil { logger.Error("用户登录失败:%v", err) err = exception.New(exception.GenerateTokenFailed) return } _ = userService.UserLoginFlow(userId) token, err = jwt.CreateToken(openId, mobile, jwt.AccessToken) if err != nil { err = exception.New(exception.GenerateTokenFailed) return } //保险先删一下 _ = rd().Delete(redis.GenerateTokenKey(openId)) err = rd().SetString(redis.GenerateTokenKey(openId), token, 90*24*60*60) if err != nil { err = exception.New(exception.GenerateTokenFailed) return } //清除手机验证码 codeDTO := smsService.CodeDTO{ Mobile: mobile, Code: verifyCode, } //登录成功删除短信验证码,数据库留痕 err = rd().Delete(redis.GenerateSmsKey(mobile)) if err != nil { logger.Error("清除redis 短信验证码失败,%v", err) err = rd().SetString(redis.GenerateSmsKey(mobile), "", 1) return } _ = smsService.TryVerifiedCode(codeDTO) return } func BindMobile(userId int, mobile string, areaCode string) (err error) { err = userService.BindUserMobile(userId, mobile, areaCode) if err != nil { err = exception.New(exception.BindMobileFailed) return } return } func RefreshToken(code string) (token string, isBindMobile int, status string, tokenType string, err error) { //注册用户 var wechatInfo wechat.WxUser //微信请求异常 wechatInfo, err = wx().Login(code) //微信客户端的异常不做处理,已经是EtaError if err != nil { logger.Error("获取微信用户信息失败,错误信息:%v", err) return } user, err := userService.GetTemplateUserByOpenId(wechatInfo.OpenId) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { err = exception.New(exception.TemplateUserFoundFailed) return } //用户记录不存在则返回unbind //未注册/未绑定 var mobile string if errors.Is(err, gorm.ErrRecordNotFound) || user.Mobile == "" { status = string(userService.UnBind) tokenType = jwt.GuestToken isBindMobile = 0 mobile = emptyPhoneNum } else { isBindMobile = 1 //已绑定(登出) if user.LoginStatus == userDao.Logout { tokenType = jwt.GuestToken mobile = emptyPhoneNum status = string(userService.Logout) } else { //已绑定(登录) tokenType = jwt.AccessToken mobile = user.Mobile status = string(userService.Login) } } //通过unionId 查询用户是否已注册,存在是因为先发生了公众号的关注,所以只需要绑定小程序openId就行 wxUser, err := userService.GetTemplateUserByUnionId(wechatInfo.UnionId) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { err = exception.New(exception.TemplateUserFoundFailed) return } if errors.Is(err, gorm.ErrRecordNotFound) { user.OpenId = wechatInfo.OpenId user.UnionId = wechatInfo.UnionId err = userService.RegisterTemplateUser(&user) if err != nil { err = exception.New(exception.TemplateUserCreateFailed) return } } else { //修改微信小程序openid err = userService.BindUserXcxOpenId(wxUser.Id, user.OpenId) if err != nil { err = exception.New(exception.TemplateUserBindFailed) return } } token, err = jwt.CreateToken(wechatInfo.OpenId, mobile, tokenType) if err != nil { logger.Error("生成token失败:%v", err) err = exception.New(exception.GenerateTokenFailed) return } err = rd().SetString(redis.GenerateTokenKey(wechatInfo.OpenId), token, 90*24*60*60) if err != nil { err = exception.New(exception.GenerateTokenFailed) return } return } func GetAreaCodes() (list []userService.AreaCodeDTO, err error) { list, err = userService.GetAreaCodes() if err != nil { err = exception.New(exception.GetAreaCodesFailed) } return } func GetValidAreaCodes() (list []string) { str := rd().GetString(redis.ValidAreaCode) if str == "" { codes, err := userService.GetValidAreaCodes() if err != nil { return []string{} } var validCodes []string for _, code := range codes { validCodes = append(validCodes, code.CodeValue) } err = rd().SetString(redis.ValidAreaCode, strings.Join(validCodes, ","), 60*60) if err != nil { logger.Error("设置Redis区号失败:%v", err) list = validCodes } list = strings.Split(rd().GetString(redis.ValidAreaCode), ",") } else { list = strings.Split(str, ",") } return } func initUser(user wechat.WxUser, dto LoginDTO) userService.UserDTO { username := generateRandomUsername(user.OpenId) if stringUtils.IsBlank(username) { username = dto.Mobile } return userService.UserDTO{ OpenId: user.OpenId, UnionId: user.UnionId, Username: username, Mobile: dto.Mobile, } } // GenerateRandomUsername 生成基于UnionId的随机用户名 func generateRandomUsername(openId string) string { // 生成一个随机的4字节二进制数据 randomBytes := make([]byte, 4) _, err := rand.Read(randomBytes) if err != nil { return "" } // 将随机字节转换为base64编码的URL安全字符串 // 并去掉可能出现的等于号 randomStr := strings.TrimRight(base64.URLEncoding.EncodeToString(randomBytes), "=") // 拼接UnionId和随机字符串 username := fmt.Sprintf("%s_%s", openId, randomStr) return username } func rd() *cache.RedisCache { if redisCache == nil { redisCache = cache.GetInstance() } return redisCache } func BindXcxOpenId(userId int, openId string) (err error) { err = userService.BindUserXcxOpenId(userId, openId) return } // 绑定微信公众号 func BindWxGzh(code string) (isBind bool, err error) { //注册用户 wxUser, err := GetWxUserInfo(code) if err != nil { logger.Error("获取微信公众号用户信息失败" + err.Error()) err = exception.New(exception.TemplateUserNotFound) return } logger.Info("GetWxUserInfo:") if wxUser.ErrCode != 0 { logger.Error("获取微信公众号用户信息失败" + wxUser.ErrMsg) err = exception.New(exception.TemplateUserNotFound) return } return BindWxGzhByOpenId(wxUser.OpenId) } // BindWxGzhByOpenId // @Description: 通过openid绑定微信公众号 // @author: Roc // @datetime 2024-08-13 13:19:49 // @param openId string // @return isBind bool // @return err error func BindWxGzhByOpenId(openId string) (isBind bool, err error) { wxUserDetail, err := GetWxUserDetail(openId) if err != nil { logger.Error("获取微信公众号用户详细信息失败" + err.Error()) err = exception.New(exception.WeChatResponseError) return } unionId := wxUserDetail.UnionID followingGzh := int(wxUserDetail.Subscribe) user, err := userService.GetTemplateUserByGzhOpenId(openId) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { err = exception.New(exception.TemplateUserFoundFailed) return } if errors.Is(err, gorm.ErrRecordNotFound) { user.GzhOpenId = openId user.UnionId = unionId user.FollowingGzh = followingGzh //判断unionid是否存在 wxUser, unionErr := userService.GetTemplateUserByUnionId(unionId) if unionErr != nil && !errors.Is(unionErr, gorm.ErrRecordNotFound) { err = exception.New(exception.TemplateUserFoundFailed) return } if errors.Is(unionErr, gorm.ErrRecordNotFound) { err = userService.RegisterTemplateUser(&user) if err != nil { logger.Info("RegisterTemplateUser,Err" + err.Error()) } } else { //修改微信小程序openid logger.Info("wxUser.Id:%d", wxUser.Id) err = userService.BindUserGzhOpenId(wxUser.Id, openId, followingGzh) if err != nil { logger.Info("BindUserGzhOpenId,Err" + err.Error()) } } } else { isBind = true err = userService.BindUserGzhOpenId(user.Id, openId, followingGzh) if err != nil { logger.Info("BindUserGzhOpenId,Err" + err.Error()) } } return } // UnSubscribeWxGzhByOpenId // @Description: 通过openid取消关注微信公众号 // @author: Roc // @datetime 2024-08-13 13:19:49 // @param openId string // @return isBind bool // @return err error func UnSubscribeWxGzhByOpenId(openId string) { logger.Info("openId:" + openId) var err error defer func() { if err != nil { logger.Info("通过openid取消关注微信公众号失败,openId:", openId, ";错误信息:", err.Error()) } }() // 通过公众号openid获取用户信息 user, err := userService.GetTemplateUserByGzhOpenId(openId) if err != nil { if err == gorm.ErrRecordNotFound { err = nil // 找不到就直接返回了 return } else { err = exception.New(exception.TemplateUserNotFound) return } } // 解绑用户 err = userService.BindUserGzhOpenId(user.Id, openId, 0) if err != nil { logger.Info("UnBindWxGzhByOpenId,Err" + err.Error()) } return }