Files
web_huangguo_web/app/service/authser/guest_enter.go
T
fish03andClaude Opus 5.5 bef9f8b4ed Initial commit
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 14:43:16 +07:00

223 lines
6.0 KiB
Go

package authser
import (
"crypto/rand"
"encoding/hex"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"videoweb/pub/common/log"
"videoweb/pub/common/useragent"
"github.com/gin-gonic/gin"
)
const guestDevIDCookie = "guest_dev_id"
const guestSkipAutoCookie = "guest_skip_auto"
// EnsureGuestOnEnter 页面进入时:无 token(未自己登录)则走游客登录并写 cookie。
// 对应上游 POST /api/app/auth/guest。成功后会把会话补进当前 Request,便于同一次 SSR 读到登录态。
// 手动退出后带 guest_skip_auto 时跳过本次;清掉标记后,下次进页再自动游客。
func EnsureGuestOnEnter(c *gin.Context) *LoginResp {
if c == nil || c.Request == nil {
return nil
}
// 已有任意会话(游客或正式账号)则跳过
if useragent.TokenFromRequest(c.Request) != "" {
return nil
}
// 手动退出:本次进页不自动游客,并清除标记,供下次进页恢复
if IsGuestSkipAuto(c) {
ClearGuestSkipAuto(c)
return nil
}
devID := ensureGuestDevID(c)
env, user, err := GuestLogin(c, GuestLoginReq{
DevID: devID,
DeviceID: devID,
Channel: guestChannel(c),
})
if err != nil {
log.Error("ensure guest on enter failed", log.E(err))
return nil
}
if env == nil || env.Code != 200 || user == nil || strings.TrimSpace(user.Token) == "" {
tip := ""
if env != nil {
tip = firstNonEmptyTip(env.Tip, env.Msg)
}
log.Error("ensure guest on enter rejected", log.E(fmt.Errorf("code=%v tip=%s", envCode(env), tip)))
return nil
}
SetAuthCookies(c, user)
ClearGuestSkipAuto(c)
patchAuthCookiesToRequest(c, user)
return user
}
// RefreshGuestSession 游客会话失效时(如上游 6001 用户不存在)重新游客登录。
// 正式注册账号不处理,避免把用户踢成游客。
func RefreshGuestSession(c *gin.Context) *LoginResp {
if c == nil || c.Request == nil {
return nil
}
if IsRegisteredFromCookies(c) {
return nil
}
dropRequestCookie(c, "token")
dropRequestCookie(c, "_token_")
return EnsureGuestOnEnter(c)
}
func dropRequestCookie(c *gin.Context, name string) {
if c == nil || c.Request == nil || name == "" {
return
}
var parts []string
for _, ck := range c.Request.Cookies() {
if ck == nil || ck.Name == name {
continue
}
parts = append(parts, ck.Name+"="+ck.Value)
}
c.Request.Header.Set("Cookie", strings.Join(parts, "; "))
}
// MarkGuestSkipAuto 手动退出后标记:跳过紧接着的一次自动游客登录。
func MarkGuestSkipAuto(c *gin.Context) {
if c == nil {
return
}
secure := c.Request != nil && c.Request.TLS != nil
http.SetCookie(c.Writer, &http.Cookie{
Name: guestSkipAutoCookie,
Value: "1",
Path: "/",
MaxAge: 0, // session cookie
HttpOnly: false,
Secure: secure,
SameSite: http.SameSiteLaxMode,
})
}
// IsGuestSkipAuto 是否处于「手动退出后跳过自动游客」状态。
func IsGuestSkipAuto(c *gin.Context) bool {
return cookieValue(c, guestSkipAutoCookie) == "1"
}
// ClearGuestSkipAuto 清除手动退出跳过标记。
func ClearGuestSkipAuto(c *gin.Context) {
if c == nil {
return
}
http.SetCookie(c.Writer, &http.Cookie{
Name: guestSkipAutoCookie,
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: false,
SameSite: http.SameSiteLaxMode,
})
}
func ensureGuestDevID(c *gin.Context) string {
if id := cookieValue(c, guestDevIDCookie); id != "" {
return id
}
id := newGuestDevID()
secure := c.Request != nil && c.Request.TLS != nil
setCookie(c, guestDevIDCookie, id, secure)
patchRequestCookie(c, guestDevIDCookie, id)
return id
}
func newGuestDevID() string {
b := make([]byte, 8)
if _, err := rand.Read(b); err != nil {
now := time.Now()
return fmt.Sprintf("%016X%d", uint64(now.UnixNano()), now.UnixMilli())
}
return strings.ToUpper(hex.EncodeToString(b)) + strconv.FormatInt(time.Now().UnixMilli(), 10)
}
// guestChannel 为首屏服务端自动游客登录补渠道;cookie 用于复用前端首次归因结果。
func guestChannel(c *gin.Context) string {
if c == nil || c.Request == nil {
return ""
}
if channel := cookieValue(c, "channel"); channel != "" {
return channel
}
for _, key := range []string{"dc", "ch", "pc"} {
if channel := strings.TrimSpace(c.Query(key)); channel != "" {
secure := c.Request.TLS != nil
setCookie(c, "channel", channel, secure)
patchRequestCookie(c, "channel", channel)
return channel
}
}
return ""
}
func patchAuthCookiesToRequest(c *gin.Context, user *LoginResp) {
if c == nil || user == nil {
return
}
patchRequestCookie(c, "token", user.Token)
patchRequestCookie(c, "userId", strconv.FormatInt(user.UserID, 10))
patchRequestCookie(c, "accountNo", user.AccountNo)
patchRequestCookie(c, "nickName", user.NickName)
patchRequestCookie(c, "introduction", user.Introduction)
patchRequestCookie(c, "avatar", user.Avatar)
patchRequestCookie(c, "email", user.Email)
patchRequestCookie(c, "inviteCode", user.InviteCode)
patchRequestCookie(c, "recharge", strconv.FormatInt(user.Recharge, 10))
patchRequestCookie(c, "vipType", strconv.Itoa(user.VipType))
patchRequestCookie(c, "currentVipExpire", user.CurrentVipExpire)
patchRequestCookie(c, "showCardId", strconv.FormatInt(user.ShowCardID, 10))
patchRequestCookie(c, "showCardName", user.ShowCardName)
patchRequestCookie(c, "userType", strconv.Itoa(user.Type))
isReg := "0"
if user.IsRegister || user.Type == 1 {
isReg = "1"
}
patchRequestCookie(c, "isRegister", isReg)
}
func patchRequestCookie(c *gin.Context, name, value string) {
if c == nil || c.Request == nil || name == "" {
return
}
escaped := url.PathEscape(value)
var parts []string
for _, ck := range c.Request.Cookies() {
if ck == nil || ck.Name == name {
continue
}
parts = append(parts, ck.Name+"="+ck.Value)
}
parts = append(parts, name+"="+escaped)
c.Request.Header.Set("Cookie", strings.Join(parts, "; "))
}
func envCode(env *APIEnvelope) int {
if env == nil {
return 0
}
return env.Code
}
func firstNonEmptyTip(values ...string) string {
for _, v := range values {
if strings.TrimSpace(v) != "" {
return v
}
}
return ""
}