package user import ( "context" "yunyan/comm" "yunyan/lego/sys/mysql" "yunyan/lego/sys/wordfilter" "yunyan/lego/utils/container/id" "yunyan/pb" apple_auth "yunyan/sys/auth/apple" facebook_auth "yunyan/sys/auth/facebook" google_auth "yunyan/sys/auth/google" wechat_auth "yunyan/sys/auth/wechat" "yunyan/sys/ipinfo" "fmt" "time" "firebase.google.com/go/v4/auth" "github.com/golang-jwt/jwt/v4" "golang.org/x/exp/rand" ) // @Summary 登入接口 // @Description 根据登录类型实现登录操作 // @Tags User // @Accept json // @Produce json // @Param user body pb.UserSginReq true "登录请求" // @Success 200 {object} comm.HttpResult{data=pb.UserSginResp} "成功返回用户信息" // @Router /api/home/user_sgin [post] func (this *apiComp) Sgin(session comm.IUserSession, req *pb.UserSginReq) (resp *pb.UserSginResp, errdata *pb.ErrorData) { var ( istestaccount bool code string apple_uuid string wxinfo *wechat_auth.WeChatUserInfoResponse googleinfo *auth.Token facebookinfo *facebook_auth.FacebookTokenInfo // firebaseinfo *auth.Token ipdata *ipinfo.IPData user *pb.DBUser divices []*pb.DBUserDivice tokenString string err error ) // this.module.Infof("登陆日志 options:%v req:%v", this.module.options, req) switch req.Stype { case pb.SginTyoe_Mail: //邮箱 if this.module.options.OpenTestAccount { for _, account := range this.module.options.TestAccounts { // this.module.Infof("登录日志 测试账号 account:%s req.Mail %s TestAccountPassword:%s Vcode:%s", account, req.Mail, this.module.options.TestAccountPassword, req.Vcode) if account == req.Mail && this.module.options.TestAccountPassword == req.Vcode { istestaccount = true // this.module.Infof("登录日志 测试账号登录 account:%s", account) break } } } if !istestaccount { //并非测试账号 if code, err = this.module.model.getCode(req.Mail); err != nil { // this.module.Infof("登录日志 获取验证码失败 account:%s err:%v", req.Mail, err) errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } if code != req.Vcode { errdata = &pb.ErrorData{ Code: pb.ErrorCode_ReqParameterError, Message: "code is error", } return } } if user, err = this.module.model.findformail(req.Mail); err != nil && err != mysql.ErrNoDocuments { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } case pb.SginTyoe_Phone: //手机 if this.module.options.OpenTestAccount { for _, account := range this.module.options.TestAccounts { if account == req.Phone && this.module.options.TestAccountPassword == req.Vcode { istestaccount = true break } } } if !istestaccount { //并非测试账号 if code, err = this.module.model.getCode(req.Phone); err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } if code != req.Vcode { errdata = &pb.ErrorData{ Code: pb.ErrorCode_ReqParameterError, Message: "code is error", } return } } if user, err = this.module.model.findforphone(req.Phone); err != nil && err != mysql.ErrNoDocuments { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } case pb.SginTyoe_WXChat: //微信 if wxinfo, err = wechat_auth.Auth(context.Background(), req.Ttoken); err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_ExceptionAccountVerification, Message: err.Error(), } return } if user, err = this.module.model.findforwx(wxinfo.OpenID); err != nil && err != mysql.ErrNoDocuments { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } case pb.SginTyoe_Google: //微信 if googleinfo, err = google_auth.Auth(context.Background(), req.Ttoken); err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_ExceptionAccountVerification, Message: err.Error(), } return } if user, err = this.module.model.findforgoogle(googleinfo.UID); err != nil && err != mysql.ErrNoDocuments { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } case pb.SginTyoe_FaceBook: //微信 if facebookinfo, err = facebook_auth.Auth(context.Background(), req.Ttoken); err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_ExceptionAccountVerification, Message: err.Error(), } return } if user, err = this.module.model.findforfacebook(facebookinfo.UserID); err != nil && err != mysql.ErrNoDocuments { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } case pb.SginTyoe_Apple: //微信 if apple_uuid, err = apple_auth.Auth(context.Background(), req.Ttoken); err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_ExceptionAccountVerification, Message: err.Error(), } return } if user, err = this.module.model.findforapple(apple_uuid); err != nil && err != mysql.ErrNoDocuments { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } case pb.SginTyoe_Tourists: //游客登录 // 按设备硬件 id 找回同一个游客账号。 // // 这段原先是注释掉的,恒定走下面的"新建账号"分支,后果有两个: // 游客每次登录都是新账号,实名认证记录留不住(客户端要求"实名过就能用功能" // 就永远不成立);而且新账号分支紧接着就发新用户开户礼,等于可以无限白嫖。 if req.Phonemac == "" { errdata = &pb.ErrorData{ Code: pb.ErrorCode_ReqParameterError, Message: "游客登录需要设备标识", } return } if user, err = this.module.model.findtouristformac(req.Phonemac); err != nil && err != mysql.ErrNoDocuments { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } default: errdata = &pb.ErrorData{ Code: pb.ErrorCode_ReqParameterError, Message: "stype is no support", } return } if err == mysql.ErrNoDocuments { user = &pb.DBUser{ Uid: id.NewSnowflakeId(int64(this.options.UserGroup)), Phone: req.Phone, Mail: req.Mail, Createtime: time.Now().Unix(), Language: req.Language, Phonemodel: req.Phonemodel, Phonemac: req.Phonemac, Avatar: req.Avatar, Name: req.Name, Channel: req.Channel, Version: req.Version, } switch req.Stype { case pb.SginTyoe_WXChat: user.Wxopenid = wxinfo.OpenID user.Name = wxinfo.Nickname user.Avatar = wxinfo.HeadImgURL case pb.SginTyoe_Google: user.Googleopenid = googleinfo.UID case pb.SginTyoe_FaceBook: user.Facebookopenid = facebookinfo.UserID case pb.SginTyoe_Apple: user.Appleopenid = apple_uuid } // if user.Phone != "" { // user.Phone = aes.AesEncryptCBC(user.Phone, this.options.CBCKey) // } // if user.Mail != "" { // user.Mail = aes.AesEncryptCBC(user.Mail, this.options.CBCKey) // } user.Name = cleanDisplayName(user.Name) // user.Name = aes.AesEncryptCBC(user.Name, this.options.CBCKey) // if user.Avatar != "" { // user.Avatar = aes.AesEncryptCBC(user.Avatar, this.options.CBCKey) // } // 新用户开户礼:VIP 天数 + 算力,两个数额由后台「会员与算力」页配置。 // 在 add 之前赋值,随用户一起落库,不需要额外一次写。见 newuser_gift.go。 // 参数取一次传给发放与流水两处,保证两者用同一份快照。 giftNow := time.Now().Unix() giftParams := comm.LoadOpsParams() applyNewUserGift(user, giftNow, giftParams) if err = this.module.model.add(user); err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } logNewUserGift(user.Uid, giftNow, giftParams) // 新增用户埋点 if this.module.analyze != nil { this.module.analyze.Report(&comm.StatEvent{Type: comm.StatEventRegister, Uid: user.Uid}) } } else if user.State == pb.UserState_Disabled { // 账号已被禁用,拒绝登录 errdata = &pb.ErrorData{ Code: pb.ErrorCode_ExceptionAccountVerification, Message: "account disabled", } return } else if user.State == pb.UserState_Cancelled { user.State = pb.UserState_Normal user.Canceltime = 0 switch req.Stype { case pb.SginTyoe_WXChat: user.Wxopenid = wxinfo.OpenID user.Name = wxinfo.Nickname user.Avatar = wxinfo.HeadImgURL case pb.SginTyoe_Google: user.Googleopenid = googleinfo.UID case pb.SginTyoe_FaceBook: user.Facebookopenid = facebookinfo.UserID case pb.SginTyoe_Apple: user.Appleopenid = apple_uuid } user.Name = cleanDisplayName(user.Name) } if istestaccount { user.Vipexptime = time.Now().AddDate(1, 0, 0).Unix() user.Aichattotalintegral = 1000000 user.Tradeintegral = 60 * 60 * 24 * 365 user.Tradetotalintegral = user.Tradeintegral user.Meetintegral = 60 * 60 * 24 * 365 user.Meettotalintegral = user.Meetintegral } user.Lastsgintime = time.Now().Unix() user.Channel = req.Channel user.Version = req.Version if ipdata, _ = ipinfo.GetIPInfo(session.GetIP()); ipdata != nil { user.ContinentCode = ipdata.ContinentCode user.CountryCode = ipdata.CountryCode } claims := &jwt.RegisteredClaims{ Issuer: "Earphone", Subject: "Sgin", // ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)), // 24 hours expiration NotBefore: jwt.NewNumericDate(time.Now()), IssuedAt: jwt.NewNumericDate(time.Now()), ID: user.Uid, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) if tokenString, err = token.SignedString([]byte(this.options.TokenKey)); err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_ReqParameterError, Message: pb.ErrorCode_ReqParameterError.String(), } this.module.Errorln(err) return } if err = this.module.model.save(user); err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } // 登录埋点 if this.module.analyze != nil { // 登录归到用户最后绑定设备的产品/渠道商名下,渠道商后台才看得到自己带来的用户活跃。 this.module.analyze.Report(&comm.StatEvent{Type: comm.StatEventLogin, ProductId: user.Lastbindproductid, ChannelId: user.Lastbindchannelid, Uid: user.Uid}) } divices, err = this.module.model.getDevices(user.Uid) if err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } resp = &pb.UserSginResp{ Token: tokenString, User: user, Devices: divices, } return } // cleanDisplayName 去掉空名和敏感词。登录不能因为微信昵称不干净就拒绝建号, // 换成 User_xxxx,之后可以在资料页再改一个干净的。 func cleanDisplayName(name string) string { if name != "" { if ok, _ := wordfilter.Validate(name); ok { return name } } return fmt.Sprintf("User_%d", rand.Intn(10000)) }