feat: 新增个人中心功能

- 修改昵称改为个人中心
- 个人中心包含:账号(只读)、昵称、邮箱
- 修改邮箱需要旧邮箱验证码验证
- 后端新增 /auth/verify-email 和 /auth/change-email API
- 两个页面同步更新
This commit is contained in:
OP
2026-06-29 00:35:47 +08:00
parent b9b7e2637a
commit 1aac03b7a4
3 changed files with 337 additions and 3 deletions

View File

@@ -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,