Files
hadja_server/app/service/authser/guest.go
T
rootandClaude Opus 4.7 79ac89b458 Initial commit
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-11-07 10:47:18 +08:00

263 lines
7.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
_ = 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 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
}