feat: 新增个人中心功能
- 修改昵称改为个人中心 - 个人中心包含:账号(只读)、昵称、邮箱 - 修改邮箱需要旧邮箱验证码验证 - 后端新增 /auth/verify-email 和 /auth/change-email API - 两个页面同步更新
This commit is contained in:
@@ -129,6 +129,14 @@ async def list_groups(db: AsyncSession = Depends(get_db)):
|
||||
return [{"id": g.id, "name": g.name} for g in result.scalars().all()]
|
||||
|
||||
|
||||
|
||||
|
||||
class EmailVerifyRequest(BaseModel):
|
||||
email: str
|
||||
|
||||
class EmailChangeRequest(BaseModel):
|
||||
code: str
|
||||
new_email: str
|
||||
class ProfileUpdate(BaseModel):
|
||||
nickname: str | None = None
|
||||
real_email: str | None = None
|
||||
@@ -148,6 +156,83 @@ async def update_profile(
|
||||
return {"message": "资料更新成功"}
|
||||
|
||||
|
||||
|
||||
|
||||
@router.post("/verify-email")
|
||||
async def send_email_verification(
|
||||
req: EmailVerifyRequest,
|
||||
user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
import secrets
|
||||
from datetime import timedelta
|
||||
code = f"{secrets.randbelow(1000000):06d}"
|
||||
key = f"email_verify:{user.id}"
|
||||
value = f"{req.email}|{code}|{(datetime.utcnow() + timedelta(minutes=5)).isoformat()}"
|
||||
|
||||
result = await db.execute(select(SystemSetting).where(SystemSetting.key == key))
|
||||
setting = result.scalar_one_or_none()
|
||||
if setting:
|
||||
setting.value = value
|
||||
else:
|
||||
db.add(SystemSetting(key=key, value=value))
|
||||
await db.commit()
|
||||
|
||||
if user.real_email:
|
||||
from app.services.email_service import send_email
|
||||
html = f"""
|
||||
<div style="font-family:-apple-system,sans-serif;max-width:480px;margin:0 auto;padding:32px">
|
||||
<h2 style="color:#0071e3;margin-bottom:16px">邮箱变更验证码</h2>
|
||||
<p>Hi {user.nickname or user.email},</p>
|
||||
<p>您正在修改绑定邮箱,验证码如下:</p>
|
||||
<div style="background:#f5f5f7;border-radius:12px;padding:24px;margin:16px 0;text-align:center">
|
||||
<p style="font-size:36px;font-weight:700;letter-spacing:8px;color:#1d1d1f;margin:0">{code}</p>
|
||||
<p style="color:#6e6e73;font-size:12px;margin:8px 0 0 0">5 分钟内有效</p>
|
||||
</div>
|
||||
<p style="color:#6e6e73;font-size:13px">如非本人操作,请忽略此邮件。</p>
|
||||
</div>
|
||||
"""
|
||||
await send_email(user.real_email, "【债务管理系统】邮箱变更验证码", html)
|
||||
|
||||
return {"message": "验证码已发送到当前邮箱"}
|
||||
|
||||
|
||||
@router.post("/change-email")
|
||||
async def change_email(
|
||||
req: EmailChangeRequest,
|
||||
user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
key = f"email_verify:{user.id}"
|
||||
result = await db.execute(select(SystemSetting).where(SystemSetting.key == key))
|
||||
setting = result.scalar_one_or_none()
|
||||
if not setting:
|
||||
raise HTTPException(status_code=400, detail="验证码已过期,请重新获取")
|
||||
|
||||
parts = setting.value.split("|")
|
||||
if len(parts) != 3 or parts[1] != req.code:
|
||||
raise HTTPException(status_code=400, detail="验证码错误")
|
||||
|
||||
try:
|
||||
expires_at = datetime.fromisoformat(parts[2])
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="验证码格式错误")
|
||||
|
||||
if datetime.utcnow() > expires_at:
|
||||
await db.delete(setting)
|
||||
await db.commit()
|
||||
raise HTTPException(status_code=400, detail="验证码已过期,请重新获取")
|
||||
|
||||
# Check if new email is already used
|
||||
existing = await db.execute(select(User).where(User.real_email == req.new_email, User.id != user.id))
|
||||
if existing.scalar_one_or_none():
|
||||
raise HTTPException(status_code=400, detail="该邮箱已被其他账号使用")
|
||||
|
||||
user.real_email = req.new_email
|
||||
await db.delete(setting)
|
||||
await db.commit()
|
||||
return {"message": "邮箱修改成功"}
|
||||
|
||||
@router.put("/password")
|
||||
async def change_password(
|
||||
req: ChangePasswordRequest,
|
||||
|
||||
Reference in New Issue
Block a user