Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
277 lines
7.8 KiB
Go
277 lines
7.8 KiB
Go
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,
|
||
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
|
||
}
|
||
}
|