package authser import ( "context" "fmt" "strings" "time" "videoweb/app/appg" "videoweb/app/middleware/reptilerecord" "videoweb/app/middleware/visit" "videoweb/app/service/configser" "videoweb/app/service/reportser" "videoweb/pub/common/log" "videoweb/pub/common/netutil" "videoweb/pub/common/srvdef" "videoweb/pub/common/useragent" "videoweb/pub/models/usermod" "videoweb/pub/mongos" "videoweb/pub/pubdef" "github.com/gin-gonic/gin" "go.mongodb.org/mongo-driver/bson" ) // GuestLoginReq 游客登录请求 type GuestLoginReq struct { DevID string `json:"devID"` // 设备唯一标示(必填) Mac string `json:"mac"` // 设备 Mac(可选) DeviceId string `json:"deviceId"` // 备用设备ID(可选,写入 DevV2) Channel string `json:"channel"` // 渠道码(可选,用户无商区码时写入 DistrictCode) } // isGuestUser 是否为可升级的游客身份 func isGuestUser(user *usermod.User) bool { if user == nil { return false } if user.Type == pubdef.UserTypeNormal { return false } return !user.IsRegister && strings.TrimSpace(user.AccountNo) == "" } // GuestLogin 游客登录:按 token / DevID / Mac / DeviceId 查找,不存在则创建游客 func GuestLogin(ctx context.Context, userId int64, ip string, req *GuestLoginReq, ua useragent.UA) (*LoginResp, *srvdef.SrvErr) { if isBotRequest(ctx) { return crawlerGuestLogin() } if req == nil || strings.TrimSpace(req.DevID) == "" { return nil, srvdef.New(srvdef.ErrParamError, "devID不能为空", "") } ua.DevID = strings.TrimSpace(req.DevID) var user *usermod.User var serr *srvdef.SrvErr reuseToken := false if userId > 0 { user, serr = usermod.FindOneUser(ctx, bson.M{usermod.FN.User.Id: userId}) if serr != nil { if serr.Code != srvdef.ErrUserNotExsit { return nil, serr } userId = 0 user = nil } else { reuseToken = true } } isNewUser := false if userId == 0 { user, serr = findGuestUser(ctx, req) if serr != nil { if serr.Code != srvdef.ErrUserNotExsit { return nil, serr } user, serr = createGuestUser(ctx, ua, req, ip) if serr != nil { return nil, serr } isNewUser = true } } if user.Status == pubdef.UserStatusDisable { return nil, srvdef.New(srvdef.AccountErrAccessForbid, srvdef.AccountErrAccessForbid.Msg(), "") } now := time.Now() setDoc := bson.M{ usermod.FN.User.LastLoginAt: now, usermod.FN.User.UpdatedAt: now, usermod.FN.User.LastIp: ip, usermod.FN.User.LoginType: pubdef.LoginTypeDevice, } if v := strings.TrimSpace(ua.DevID); v != "" { setDoc[usermod.FN.User.DevID] = v } if v := strings.TrimSpace(ua.DevType); v != "" { setDoc[usermod.FN.User.Device] = v } if v := strings.TrimSpace(ua.SysType); v != "" { setDoc[usermod.FN.User.SysType] = v } if v := strings.TrimSpace(ua.Ver); v != "" { setDoc[usermod.FN.User.AppVer] = v } if v := strings.TrimSpace(req.Mac); v != "" { setDoc[usermod.FN.User.Mac] = v } if v := strings.TrimSpace(req.DeviceId); v != "" { setDoc[usermod.FN.User.DevV2] = v } // 历史用户 Type 为空/旧值时,未注册则归为游客 if isGuestUser(user) && user.Type != pubdef.UserTypeGuest { setDoc[usermod.FN.User.Type] = pubdef.UserTypeGuest user.Type = pubdef.UserTypeGuest } mergeUAExtFieldsToSet(setDoc, user, ua) applyChannelIfMissing(setDoc, user, req.Channel) _ = usermod.UpdateOneUser(ctx, bson.M{usermod.FN.User.Id: user.Id}, bson.M{"$set": setDoc}) var token string if reuseToken { if old, err := tokener.Get(user.Id); err == nil && len(old) > 0 { token = string(old) } } if token == "" { token, serr = generateAndSaveToken(user.Id) if serr != nil { return nil, serr } } resp := toLoginResp(ctx, user, token, true) resp.IsNewUser = isNewUser if isNewUser { visit.RecordDailyVisit(ctx, user.Id, ua, ip) reportser.ReportUserRegister(user, ua, ip, "deviceid") } reportser.ReportUserLogin(user, ua, ip, pubdef.LoginTypeDevice) return resp, nil } func findGuestUser(ctx context.Context, req *GuestLoginReq) (*usermod.User, *srvdef.SrvErr) { user, serr := usermod.FindOneUser(ctx, bson.M{usermod.FN.User.DevID: req.DevID}) if serr == nil { return user, nil } if serr.Code != srvdef.ErrUserNotExsit { return nil, serr } mac := strings.TrimSpace(req.Mac) if mac != "" && !strings.Contains(mac, "00:00:00:00") { user, serr = usermod.FindOneUser(ctx, bson.M{usermod.FN.User.Mac: mac}) if serr == nil { return user, nil } if serr.Code != srvdef.ErrUserNotExsit { return nil, serr } } deviceId := strings.TrimSpace(req.DeviceId) if deviceId != "" && !strings.Contains(deviceId, "00:00:00:00") { return usermod.FindOneUser(ctx, bson.M{usermod.FN.User.DevV2: deviceId}) } return nil, srvdef.New(srvdef.ErrUserNotExsit, "", "") } func createGuestUser(ctx context.Context, ua useragent.UA, req *GuestLoginReq, ip string) (*usermod.User, *srvdef.SrvErr) { now := time.Now() uid := mongos.Lid.User.Get() districtInfo := resolveUserDistrict(req.Channel, "", ip, ua.SysType) user := &usermod.User{ Id: uid, Type: pubdef.UserTypeGuest, Status: pubdef.UserStatusNormal, LoginType: pubdef.LoginTypeDevice, IsRegister: false, NickName: fmt.Sprintf("游客%d", uid), Avatar: randomAvatar(), InviteCode: generateUniqueInviteCode(ctx), DistrictCode: normalizeChannel(req.Channel), IsDirect: districtInfo.IsDirect, RegIp: ip, LastIp: ip, DevID: ua.DevID, Mac: strings.TrimSpace(req.Mac), DevV2: strings.TrimSpace(req.DeviceId), Device: ua.DevType, SysType: ua.SysType, AppVer: ua.Ver, OriVer: ua.Ver, SessionID: ua.SessionID, DeviceBrand: ua.DeviceBrand, DeviceModel: ua.DeviceModel, SystemName: ua.SystemName, SystemVersion: ua.SystemVersion, LastLoginAt: now, CreatedAt: now, UpdatedAt: now, } if nick := appg.GenerateNickname(); nick != "" { user.NickName = nick } if serr := usermod.InsertOneUser(ctx, user); serr != nil { log.Error("authser:createGuestUser InsertOneUser failed", log.E(serr), log.Any("devID", ua.DevID)) return nil, serr } return user, nil } func toLoginResp(ctx context.Context, user *usermod.User, token string, withCDN bool) *LoginResp { avatar := user.Avatar if withCDN { cdn := configser.GetAppImageCdn(configser.ClientIPFromCtx(ctx)) avatar = netutil.MergeURL(cdn, user.Avatar) } return &LoginResp{ Token: token, UserId: user.Id, AccountNo: user.AccountNo, Email: user.Email, NickName: user.NickName, Introduction: user.Introduction, VipType: user.VipType, CurrentVipExpire: user.CurrentVipExpire, ShowCardId: user.ShowCardId, ShowCardName: user.ShowCardName, Status: user.Status, Avatar: avatar, InviteCode: user.InviteCode, Type: user.Type, LoginType: user.LoginType, IsRegister: user.IsRegister, PrivacyMode: user.PrivacyMode, HasPassword: strings.TrimSpace(user.Password) != "", Score: user.Score, Recharge: user.Recharge, } } func normalizeChannel(channel string) string { return strings.TrimSpace(channel) } func hasDistrictCode(code string) bool { return strings.TrimSpace(code) != "" } func isBotRequest(ctx context.Context) bool { gc, ok := ctx.(*gin.Context) return ok && reptilerecord.IsBot(gc) } // crawlerGuestLogin 给爬虫签发可观看的虚拟游客 token,不建用户、不写日活、不上报。 func crawlerGuestLogin() (*LoginResp, *srvdef.SrvErr) { token, serr := generateCrawlerToken() if serr != nil { return nil, serr } return CrawlerLoginResp(token), nil } func applyChannelIfMissing(setDoc bson.M, user *usermod.User, channel string) { if user == nil || hasDistrictCode(user.DistrictCode) { return } if ch := normalizeChannel(channel); ch != "" { setDoc[usermod.FN.User.DistrictCode] = ch user.DistrictCode = ch } }