Files
web_huakui_server/app/service/authser/guest.go
T
rootandClaude Opus 4.7 e7179da072 feat: 同步 huangguo2-server 的 11 个非基线提交
按意图重做在 huakui 代码底:
- 删除数据埋点逻辑:移除 pub/dataReport、reportser、srv_report、
  eventmod、上报定时任务及注册/登录/点赞/收藏/评论/解锁/订单处的
  上报调用和 reportSdkConfig 配置下发
- app 认证改为按设备ID识别用户:去掉 token 校验、Register/Login/
  ResetPassword/GuestLogin 接口,新增 ResolveDeviceUser 和设备缓存
- 广告改为按 AppId 从媒资中心拉取:pub/storage 新增 QueryAdvitise,
  UpdateAdvitiseData 替代 UpdateAdvitiseDataFromJt
- 后台登录取消谷歌二次验证,密码正确即登录
- 短剧/专题 slug 改为 10 位大小写字母+数字混合
- 本地 app.yaml/web.yaml 保留 HKDJNET_* 模板(不提交生产凭据)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-16 12:59:14 +08:00

195 lines
6.0 KiB
Go

package authser
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"hkdj-web-server/app/appg"
"hkdj-web-server/app/service/configser"
"hkdj-web-server/pub/common/enum/redisconst"
"hkdj-web-server/pub/common/log"
"hkdj-web-server/pub/common/netutil"
"hkdj-web-server/pub/common/srvdef"
"hkdj-web-server/pub/common/useragent"
"hkdj-web-server/pub/models/usermod"
"hkdj-web-server/pub/mongos"
"hkdj-web-server/pub/pubdef"
"hkdj-web-server/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,
IsRegister: false,
LoginType: pubdef.LoginTypeDevice,
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 user.NickName == "" {
user.NickName = appg.GenerateNickname()
}
if serr := usermod.InsertOneUser(ctx, user); serr != nil {
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,
Avatar: avatar,
Introduction: user.Introduction,
DevID: user.DevID,
Mac: user.Mac,
DeviceId: user.DevV2,
InviteCode: user.InviteCode,
AgentInviteCode: user.AgentInviteCode,
Invites: user.Invites,
Status: user.Status,
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 applyChannelIfMissing(setDoc bson.M, user *usermod.User, channel string) {
if user == nil || hasDistrictCode(user.DistrictCode) {
return
}
code := normalizeChannel(channel)
if code == "" {
return
}
setDoc[usermod.FN.User.DistrictCode] = code
districtInfo := resolveUserDistrict(channel, user.AgentInviteCode, user.LastIp, user.SysType)
setDoc[usermod.FN.User.IsDirect] = districtInfo.IsDirect
}