feat(user): support login with any case of username & email

This commit is contained in:
MingxuanGame
2025-08-26 11:39:55 +00:00
parent 8d9f1d2750
commit f8535fdce4
4 changed files with 66 additions and 10 deletions

View File

@@ -111,13 +111,15 @@ async def authenticate_user_legacy(db: AsyncSession, name: str, password: str) -
pw_md5 = hashlib.md5(password.encode()).hexdigest() pw_md5 = hashlib.md5(password.encode()).hexdigest()
# 2. 根据用户名查找用户 # 2. 根据用户名查找用户
statement = select(User).where(User.username == name).options() user = None
user = (await db.exec(statement)).first() user = (await db.exec(select(User).where(User.username == name))).first()
if not user: if user is None:
user = (await db.exec(select(User).where(User.email == name))).first()
if user is None and name.isdigit():
user = (await db.exec(select(User).where(User.id == int(name)))).first()
if user is None:
return None return None
await db.refresh(user)
# 3. 验证密码 # 3. 验证密码
if user.pw_bcrypt is None or user.pw_bcrypt == "": if user.pw_bcrypt is None or user.pw_bcrypt == "":
return None return None

View File

@@ -42,7 +42,7 @@ from fastapi import APIRouter, Depends, Form, Request
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from redis.asyncio import Redis from redis.asyncio import Redis
from sqlalchemy import text from sqlalchemy import text
from sqlmodel import select from sqlmodel import exists, select
def create_oauth_error_response(error: str, description: str, hint: str, status_code: int = 400): def create_oauth_error_response(error: str, description: str, hint: str, status_code: int = 400):
@@ -101,12 +101,12 @@ async def register_user(
email_errors = validate_email(user_email) email_errors = validate_email(user_email)
password_errors = validate_password(user_password) password_errors = validate_password(user_password)
result = await db.exec(select(User).where(User.username == user_username)) result = await db.exec(select(exists()).where(User.username == user_username))
existing_user = result.first() existing_user = result.first()
if existing_user: if existing_user:
username_errors.append("Username is already taken") username_errors.append("Username is already taken")
result = await db.exec(select(User).where(User.email == user_email)) result = await db.exec(select(exists()).where(User.email == user_email))
existing_email = result.first() existing_email = result.first()
if existing_email: if existing_email:
email_errors.append("Email is already taken") email_errors.append("Email is already taken")

View File

@@ -11,7 +11,7 @@ from app.utils import utcnow
from .router import router from .router import router
from fastapi import Body, HTTPException, Security from fastapi import Body, HTTPException, Security
from sqlmodel import select from sqlmodel import exists, select
@router.post( @router.post(
@@ -34,7 +34,7 @@ async def user_rename(
返回: 返回:
- 成功: None - 成功: None
""" """
samename_user = (await session.exec(select(User).where(User.username == new_name))).first() samename_user = (await session.exec(select(exists()).where(User.username == new_name))).first()
if samename_user: if samename_user:
raise HTTPException(409, "Username Exisits") raise HTTPException(409, "Username Exisits")
errors = validate_username(new_name) errors = validate_username(new_name)

View File

@@ -0,0 +1,54 @@
"""user: change collation for username and email
Revision ID: af88493881eb
Revises: 34a563187e47
Create Date: 2025-08-26 11:31:07.183273
"""
from __future__ import annotations
from collections.abc import Sequence
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "af88493881eb"
down_revision: str | Sequence[str] | None = "34a563187e47"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
"""Upgrade schema."""
# 1. 删除现有索引
op.drop_index("ix_lazer_users_email", table_name="lazer_users")
op.drop_index("ix_lazer_users_username", table_name="lazer_users")
# 2. 修改字段 collation 为 utf8mb4_general_ci
op.execute("""
ALTER TABLE lazer_users
MODIFY username VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
MODIFY email VARCHAR(254) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;
""")
# 3. 重新创建唯一索引(大小写不敏感)
op.create_index(op.f("ix_lazer_users_email"), "lazer_users", ["email"], unique=True)
op.create_index(op.f("ix_lazer_users_username"), "lazer_users", ["username"], unique=True)
def downgrade() -> None:
# 1. 删除索引
op.drop_index("ix_lazer_users_email", table_name="lazer_users")
op.drop_index("ix_lazer_users_username", table_name="lazer_users")
# 2. 恢复原 collation假设原来是 utf8mb4_bin
op.execute("""
ALTER TABLE lazer_users
MODIFY username VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
MODIFY email VARCHAR(254) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL;
""")
# 3. 恢复原索引
op.create_index(op.f("ix_lazer_users_email"), "lazer_users", ["email"], unique=True)
op.create_index(op.f("ix_lazer_users_username"), "lazer_users", ["username"], unique=True)