Files
web_hgdjb_server/app/service/authser/guest.go
T
rootandClaude Opus 4.7 cd8dea8787 同步 web_hga 的五项改造
- 移除 买点 (user acquisition tracking) 子系统:dataReport SDK、eventmod
  事件模型、reportdata 定时上报 job、reportser/srv_report 包装层,以及业务
  侧的 ~20 处 Report* 调用点;去掉 proto.ConfigInfo 的 ReportSDKConfig
  字段、DataReport yaml 配置块和 pubdef.TrackerAppId。
- 广告获取改走 versionUrl /api/stat/advitise/v2/get(appId=12),移除
  adcenter(Jt) 调用;新增 POST /api/app/ads/click 异步上报;
  proto.AdvitiseData 别名 proxy.AdvitiseData,AList/BList 改名
  AppInfoAList/AppInfoBList。
- web 后台登录取消谷歌验证码(srv_mwacc.AccLogin 去掉 code 参数),密码
  正确即发放 token。
- app 端取消注册/登录/找回密码/游客登录接口及密码、token 相关代码;鉴权
  中间件按 X-User-Agent 中的 devID 识别用户,首次访问自动建号,Redis 缓存
  10 分钟;修改资料不再支持改密码。
- app m3u8 ticket 校验失败不再降级到 fallback 路径,仍按原 m3u8Name 返回。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-11-07 11:22:25 +08:00

194 lines
6.0 KiB
Go

package authser
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"videoweb/app/appg"
"videoweb/app/service/configser"
"videoweb/pub/common/enum/redisconst"
"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"
"videoweb/pub/redis"
"go.mongodb.org/mongo-driver/bson"
)
const (
deviceUserLockRetry = 10
deviceUserLockInterval = 100 * time.Millisecond
)
// ResolveDeviceUser 按设备ID识别用户:先查缓存,再查库,都没有则自动创建
func ResolveDeviceUser(ctx context.Context, ua useragent.UA, ip, channel string) (int64, *srvdef.SrvErr) {
devID := strings.TrimSpace(ua.DevID)
if devID == "" {
return 0, srvdef.New(srvdef.ErrParamError, "devID不能为空", "")
}
ua.DevID = devID
cacheKey := redisconst.AppDeviceUserKey(devID)
if v, err := redis.Get(cacheKey); err == nil && v != nil {
if userId, perr := strconv.ParseInt(*v, 10, 64); perr == nil && userId > 0 {
return userId, nil
}
}
user, serr := findOrCreateDeviceUser(ctx, ua, ip, channel)
if serr != nil {
return 0, serr
}
if user.Status == pubdef.UserStatusDisable {
return 0, srvdef.New(srvdef.AccountErrAccessForbid, srvdef.AccountErrAccessForbid.Msg(), "")
}
if err := redis.Set(cacheKey, user.Id, redisconst.AppDeviceUserExpire); err != nil {
log.Warn("authser:ResolveDeviceUser cache set failed", log.Any("devID", devID), log.E(err))
}
return user.Id, nil
}
// findOrCreateDeviceUser 按设备ID查找用户,不存在时加锁创建,避免同一设备并发请求建出多个用户
func findOrCreateDeviceUser(ctx context.Context, ua useragent.UA, ip, channel string) (*usermod.User, *srvdef.SrvErr) {
lockKey := redisconst.AppDeviceUserLockKey(ua.DevID)
for i := 0; i < deviceUserLockRetry; i++ {
user, serr := usermod.FindOneUser(ctx, bson.M{usermod.FN.User.DevID: ua.DevID})
if serr == nil {
touchDeviceUser(ctx, user, ua, ip, channel)
return user, nil
}
if serr.Code != srvdef.ErrUserNotExsit {
return nil, serr
}
ok, err := redis.Setnx_NewOK(lockKey, 1, redisconst.AppDeviceUserLockExpire)
if err != nil {
return nil, srvdef.New(srvdef.StoreLevel, "用户创建失败", "")
}
if ok {
defer func() { _ = redis.Del(lockKey) }()
return createGuestUser(ctx, ua, ip, channel)
}
// 其他请求正在为该设备建用户,稍后重查
time.Sleep(deviceUserLockInterval)
}
return nil, srvdef.New(srvdef.StoreLevel, "用户创建失败", "")
}
// touchDeviceUser 更新最近访问信息(缓存未命中时执行,频率受缓存时长限制)
func touchDeviceUser(ctx context.Context, user *usermod.User, ua useragent.UA, ip, channel string) {
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.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
}
mergeUAExtFieldsToSet(setDoc, user, ua)
applyChannelIfMissing(setDoc, user, channel)
_ = usermod.UpdateOneUser(ctx, bson.M{usermod.FN.User.Id: user.Id}, bson.M{"$set": setDoc})
}
func createGuestUser(ctx context.Context, ua useragent.UA, ip, channel string) (*usermod.User, *srvdef.SrvErr) {
now := time.Now()
uid := mongos.Lid.User.Get()
districtInfo := resolveUserDistrict(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(channel),
IsDirect: districtInfo.IsDirect,
RegIp: ip,
LastIp: ip,
DevID: ua.DevID,
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, withCDN bool) *LoginResp {
avatar := user.Avatar
if withCDN {
cdn := configser.GetAppImageCdn(configser.ClientIPFromCtx(ctx))
avatar = netutil.MergeURL(cdn, user.Avatar)
}
return &LoginResp{
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 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
}
}