Fix: Handle 'user_agent' data truncation error
This commit is contained in:
@@ -35,7 +35,7 @@ class LoginSession(SQLModel, table=True):
|
|||||||
user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("lazer_users.id"), nullable=False, index=True))
|
user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("lazer_users.id"), nullable=False, index=True))
|
||||||
session_token: str = Field(unique=True, index=True) # 会话令牌
|
session_token: str = Field(unique=True, index=True) # 会话令牌
|
||||||
ip_address: str = Field() # 登录IP
|
ip_address: str = Field() # 登录IP
|
||||||
user_agent: str | None = Field(default=None)
|
user_agent: str | None = Field(default=None, max_length=250)
|
||||||
country_code: str | None = Field(default=None)
|
country_code: str | None = Field(default=None)
|
||||||
is_verified: bool = Field(default=False) # 是否已验证
|
is_verified: bool = Field(default=False) # 是否已验证
|
||||||
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
||||||
|
|||||||
@@ -388,13 +388,18 @@ class LoginSessionService:
|
|||||||
is_new_location: bool = False
|
is_new_location: bool = False
|
||||||
) -> LoginSession:
|
) -> LoginSession:
|
||||||
"""创建登录会话"""
|
"""创建登录会话"""
|
||||||
|
from app.utils import simplify_user_agent
|
||||||
|
|
||||||
session_token = EmailVerificationService.generate_session_token()
|
session_token = EmailVerificationService.generate_session_token()
|
||||||
|
|
||||||
|
# 简化 User-Agent 字符串
|
||||||
|
simplified_user_agent = simplify_user_agent(user_agent, max_length=250)
|
||||||
|
|
||||||
session = LoginSession(
|
session = LoginSession(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
session_token=session_token,
|
session_token=session_token,
|
||||||
ip_address=ip_address,
|
ip_address=ip_address,
|
||||||
user_agent=user_agent,
|
user_agent=simplified_user_agent,
|
||||||
country_code=country_code,
|
country_code=country_code,
|
||||||
is_new_location=is_new_location,
|
is_new_location=is_new_location,
|
||||||
expires_at=datetime.now(UTC) + timedelta(hours=24), # 24小时过期
|
expires_at=datetime.now(UTC) + timedelta(hours=24), # 24小时过期
|
||||||
|
|||||||
@@ -45,8 +45,10 @@ class LoginLogService:
|
|||||||
raw_ip = get_client_ip(request)
|
raw_ip = get_client_ip(request)
|
||||||
ip_address = normalize_ip(raw_ip)
|
ip_address = normalize_ip(raw_ip)
|
||||||
|
|
||||||
# 获取User-Agent
|
# 获取并简化User-Agent
|
||||||
user_agent = request.headers.get("User-Agent", "")
|
from app.utils import simplify_user_agent
|
||||||
|
raw_user_agent = request.headers.get("User-Agent", "")
|
||||||
|
user_agent = simplify_user_agent(raw_user_agent, max_length=500)
|
||||||
|
|
||||||
# 创建基本的登录记录
|
# 创建基本的登录记录
|
||||||
login_log = UserLoginLog(
|
login_log = UserLoginLog(
|
||||||
|
|||||||
77
app/utils.py
77
app/utils.py
@@ -146,3 +146,80 @@ def check_image(content: bytes, size: int, width: int, height: int) -> None:
|
|||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=400, detail=f"Error processing image: {e}")
|
raise HTTPException(status_code=400, detail=f"Error processing image: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def simplify_user_agent(user_agent: str | None, max_length: int = 200) -> str | None:
|
||||||
|
"""
|
||||||
|
简化 User-Agent 字符串,只保留 osu! 和关键设备系统信息浏览器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_agent: 原始 User-Agent 字符串
|
||||||
|
max_length: 最大长度限制
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
简化后的 User-Agent 字符串,或 None
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
|
||||||
|
if not user_agent:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 如果长度在限制内,直接返回
|
||||||
|
if len(user_agent) <= max_length:
|
||||||
|
return user_agent
|
||||||
|
|
||||||
|
# 提取操作系统信息
|
||||||
|
os_info = ""
|
||||||
|
os_patterns = [
|
||||||
|
r'(Windows[^;)]*)',
|
||||||
|
r'(Mac OS[^;)]*)',
|
||||||
|
r'(Linux[^;)]*)',
|
||||||
|
r'(Android[^;)]*)',
|
||||||
|
r'(iOS[^;)]*)',
|
||||||
|
r'(iPhone[^;)]*)',
|
||||||
|
r'(iPad[^;)]*)'
|
||||||
|
]
|
||||||
|
|
||||||
|
for pattern in os_patterns:
|
||||||
|
match = re.search(pattern, user_agent, re.IGNORECASE)
|
||||||
|
if match:
|
||||||
|
os_info = match.group(1).strip()
|
||||||
|
break
|
||||||
|
|
||||||
|
# 提取浏览器信息
|
||||||
|
browser_info = ""
|
||||||
|
browser_patterns = [
|
||||||
|
r'(osu![^)]*)', # osu! 客户端
|
||||||
|
r'(Chrome/[\d.]+)',
|
||||||
|
r'(Firefox/[\d.]+)',
|
||||||
|
r'(Safari/[\d.]+)',
|
||||||
|
r'(Edge/[\d.]+)',
|
||||||
|
r'(Opera/[\d.]+)'
|
||||||
|
]
|
||||||
|
|
||||||
|
for pattern in browser_patterns:
|
||||||
|
match = re.search(pattern, user_agent, re.IGNORECASE)
|
||||||
|
if match:
|
||||||
|
browser_info = match.group(1).strip()
|
||||||
|
# 如果找到了 osu! 客户端,优先使用
|
||||||
|
if 'osu!' in browser_info.lower():
|
||||||
|
break
|
||||||
|
|
||||||
|
# 构建简化的 User-Agent
|
||||||
|
parts = []
|
||||||
|
if os_info:
|
||||||
|
parts.append(os_info)
|
||||||
|
if browser_info:
|
||||||
|
parts.append(browser_info)
|
||||||
|
|
||||||
|
if parts:
|
||||||
|
simplified = '; '.join(parts)
|
||||||
|
else:
|
||||||
|
# 如果没有识别到关键信息,截断原始字符串
|
||||||
|
simplified = user_agent[:max_length-3] + "..."
|
||||||
|
|
||||||
|
# 确保不超过最大长度
|
||||||
|
if len(simplified) > max_length:
|
||||||
|
simplified = simplified[:max_length-3] + "..."
|
||||||
|
|
||||||
|
return simplified
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
"""Increase the length limit of the user_agent field in the login_sessions table
|
||||||
|
|
||||||
|
Revision ID: 5b76689f6e4b
|
||||||
|
Revises: 65e7dc8d5905
|
||||||
|
Create Date: 2025-08-22 15:14:59.242274
|
||||||
|
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "5b76689f6e4b"
|
||||||
|
down_revision: str | Sequence[str] | None = "65e7dc8d5905"
|
||||||
|
branch_labels: str | Sequence[str] | None = None
|
||||||
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column("login_sessions", "user_agent",
|
||||||
|
existing_type=mysql.VARCHAR(length=255),
|
||||||
|
type_=sa.String(length=250),
|
||||||
|
existing_nullable=True)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column("login_sessions", "user_agent",
|
||||||
|
existing_type=sa.String(length=250),
|
||||||
|
type_=mysql.VARCHAR(length=255),
|
||||||
|
existing_nullable=True)
|
||||||
|
# ### end Alembic commands ###
|
||||||
Reference in New Issue
Block a user