Files
web_huakui_server/app/service/authser/guest.go
T
rootandClaude Opus 4.7 85c0071fd8 feat: 从黄果 NET hgdjnet-server 9bfbafe 导入 HKDJ 官网后端
来源:hgdjnet-server 9bfbafe10d417deea959bd53117bc88a10e60a83
项目身份 HKDJ / JHA-216,独立官网仓库,Jenkins 守卫键 hkdjnet
测试目录 /server/hkdj_net,App/Web API 30331/30332

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-16 11:20:04 +08:00

278 lines
8.0 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"
"hkdj-web-server/app/appg"
"hkdj-web-server/app/middleware/reptilerecord"
"hkdj-web-server/app/middleware/visit"
"hkdj-web-server/app/service/configser"
"hkdj-web-server/app/service/reportser"
"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"
"strings"
"time"
"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
}
}