v2.1: 新增通知提醒功能和安全增强

功能:
- 通知提醒系统: 还款日提醒、逾期提醒、系统消息横幅、登录时通知
- 通知中心UI: 铃铛图标 + 下拉列表 + 未读徽章
- 定时任务: APScheduler 每小时检查即将到期的还款
- 邮件通知: SMTP 发送还款提醒和安全警告
- 异常登录通知: 3/5/10次失败触发网页和邮件通知

安全:
- 登录安全告警: 异常登录次数达到阈值时发送通知
- 密码重置链接: 一次性使用, 5分钟有效期
- 登录频率限制: IP级和账户级限制

技术:
- 后端: FastAPI + SQLAlchemy + PostgreSQL
- 前端: 纯 HTML/CSS/JS
- 部署: Docker Compose (新增 build 配置)
This commit is contained in:
OP
2026-06-28 18:06:56 +08:00
commit ffca168dbe
41 changed files with 4834 additions and 0 deletions

View File

View File

@@ -0,0 +1,256 @@
from pydantic import BaseModel
from datetime import datetime
class RegisterRequest(BaseModel):
account: str
password: str
nickname: str = ""
group_id: int | None = None
class LoginRequest(BaseModel):
account: str
password: str
class TokenResponse(BaseModel):
access_token: str
refresh_token: str
token_type: str = "bearer"
class RefreshRequest(BaseModel):
refresh_token: str
class UserProfile(BaseModel):
id: int
account: str
nickname: str
real_email: str = ""
role: str
status: str
group_id: int | None = None
group_name: str = ""
created_at: datetime
class Config:
from_attributes = True
class ChangePasswordRequest(BaseModel):
old_password: str
new_password: str
class GroupCreate(BaseModel):
name: str
description: str = ""
class GroupResponse(BaseModel):
id: int
name: str
description: str
member_count: int = 0
created_at: datetime
class Config:
from_attributes = True
class GroupMemberAdd(BaseModel):
user_id: int
class LoanCreate(BaseModel):
name: str
date: str
amount: float
rate: float
periods: int
method: str
purpose: str = ""
schedule: list[dict] = []
paid: list[int] = []
class LoanResponse(BaseModel):
id: int
name: str
date: str
amount: float
rate: float
periods: int
method: str
purpose: str
schedule: list[dict]
paid: list[int]
user_id: int
owner_name: str = ""
class DebtCreate(BaseModel):
creditor_name: str
amount: int
borrow_date: datetime
interest_rate: float
rate_type: str = "annual"
repayment_method: str
period_count: int
period_unit: str = "month"
notes: str = ""
class DebtUpdate(BaseModel):
creditor_name: str | None = None
amount: int | None = None
borrow_date: datetime | None = None
interest_rate: float | None = None
rate_type: str | None = None
repayment_method: str | None = None
period_count: int | None = None
period_unit: str | None = None
notes: str | None = None
class DebtResponse(BaseModel):
id: int
user_id: int
creditor_name: str
amount: int
borrow_date: datetime
interest_rate: float
rate_type: str
repayment_method: str
period_count: int
period_unit: str
status: str
notes: str
created_at: datetime
total_paid: int = 0
total_interest: int = 0
owner_name: str = ""
class Config:
from_attributes = True
class RepaymentPlanResponse(BaseModel):
id: int
debt_id: int
due_date: datetime
principal_due: int
interest_due: int
total_due: int
status: str
class Config:
from_attributes = True
class RepaymentRecordCreate(BaseModel):
debt_id: int
plan_id: int | None = None
amount: int
paid_date: datetime
notes: str = ""
class RepaymentRecordResponse(BaseModel):
id: int
debt_id: int
plan_id: int | None
amount: int
paid_date: datetime
notes: str
created_at: datetime
class Config:
from_attributes = True
class DashboardSummary(BaseModel):
total_debt: int
total_paid: int
total_remaining: int
active_count: int
overdue_count: int
paid_count: int
upcoming_payments: list[dict]
class AdminUserResponse(BaseModel):
id: int
account: str
nickname: str
real_email: str = ""
role: str
status: str
group_id: int | None = None
group_name: str = ""
created_at: datetime
debt_count: int = 0
total_amount: int = 0
class Config:
from_attributes = True
class AdminDashboard(BaseModel):
total_users: int
active_debts: int
total_amount: int
overdue_count: int
rate_distribution: dict
status_distribution: dict
class SystemSettingUpdate(BaseModel):
value: str
class NotificationResponse(BaseModel):
id: int
user_id: int
title: str
message: str
type: str
is_read: int
related_debt_id: int | None = None
related_plan_id: int | None = None
created_at: datetime
class Config:
from_attributes = True
class NotificationListResponse(BaseModel):
items: list[NotificationResponse]
total: int
unread: int
class EmailServiceConfig(BaseModel):
smtp_host: str = ""
smtp_port: int = 465
smtp_user: str = ""
smtp_password: str = ""
smtp_from: str = ""
smtp_use_tls: bool = True
class ForgotPasswordRequest(BaseModel):
account: str
class VerifyResetCodeRequest(BaseModel):
account: str
code: str
class ResetPasswordRequest(BaseModel):
account: str
code: str
new_password: str