登陆注册与忘记密码前后端与jwt配置

Change-Id: Ide4ca3ea34609fdb33ea027e28169852fa41784a
diff --git a/rhj/backend/app/models/__init__.py b/rhj/backend/app/models/__init__.py
new file mode 100644
index 0000000..179ba58
--- /dev/null
+++ b/rhj/backend/app/models/__init__.py
@@ -0,0 +1,7 @@
+from sqlalchemy.ext.declarative import declarative_base
+
+Base = declarative_base()
+
+# 先定义好 Base,再把所有 model import 进来,让 SQLAlchemy 一次性注册它们
+from .users import User
+from .email_verification import EmailVerification
diff --git a/rhj/backend/app/models/__pycache__/__init__.cpython-312.pyc b/rhj/backend/app/models/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..a0814d8
--- /dev/null
+++ b/rhj/backend/app/models/__pycache__/__init__.cpython-312.pyc
Binary files differ
diff --git a/rhj/backend/app/models/__pycache__/email_verification.cpython-312.pyc b/rhj/backend/app/models/__pycache__/email_verification.cpython-312.pyc
new file mode 100644
index 0000000..c1d6dfa
--- /dev/null
+++ b/rhj/backend/app/models/__pycache__/email_verification.cpython-312.pyc
Binary files differ
diff --git a/rhj/backend/app/models/__pycache__/users.cpython-312.pyc b/rhj/backend/app/models/__pycache__/users.cpython-312.pyc
new file mode 100644
index 0000000..58af35e
--- /dev/null
+++ b/rhj/backend/app/models/__pycache__/users.cpython-312.pyc
Binary files differ
diff --git a/rhj/backend/app/models/email_verification.py b/rhj/backend/app/models/email_verification.py
new file mode 100644
index 0000000..2d3f7da
--- /dev/null
+++ b/rhj/backend/app/models/email_verification.py
@@ -0,0 +1,189 @@
+# filepath: /home/ronghanji/api/API-TRM/rhj/backend/app/models/email_verification.py
+from . import Base
+from sqlalchemy import (
+    Column, Integer, String, Boolean, TIMESTAMP, text, ForeignKey
+)
+from sqlalchemy.orm import relationship
+from datetime import datetime, timedelta
+import secrets
+import string
+import hashlib
+import pytz
+
+
+class EmailVerification(Base):
+    __tablename__ = 'email_verifications'
+
+    id = Column(Integer, primary_key=True, autoincrement=True, comment='验证记录ID')
+    email = Column(String(100), nullable=False, comment='邮箱地址')
+    code = Column(String(255), nullable=False, comment='验证码(加密存储)')
+    type = Column(String(50), nullable=False, comment='验证类型:register, reset_password, email_change')
+    user_id = Column(Integer, ForeignKey('users.id'), nullable=True, comment='关联用户ID')
+    is_verified = Column(Boolean, nullable=False, default=False, comment='是否已验证')
+    expires_at = Column(TIMESTAMP, nullable=False, comment='过期时间')
+    created_at = Column(
+        TIMESTAMP,
+        nullable=False,
+        server_default=text('CURRENT_TIMESTAMP'),
+        comment='创建时间'
+    )
+    verified_at = Column(TIMESTAMP, nullable=True, comment='验证时间')
+
+    # 关联用户表
+    user = relationship("User", back_populates="email_verifications")
+
+    def __init__(self, email, verification_type, user_id=None, expires_minutes=15):
+        """初始化邮箱验证记录
+        
+        Args:
+            email: 邮箱地址
+            verification_type: 验证类型
+            user_id: 用户ID(可选)
+            expires_minutes: 过期时间(分钟)
+        """
+        self.email = email
+        self.type = verification_type
+        self.user_id = user_id
+        self.is_verified = False
+        
+        # 使用中国时区时间,确保与数据库时间一致
+        china_tz = pytz.timezone('Asia/Shanghai')
+        current_time = datetime.now(china_tz).replace(tzinfo=None)
+        self.expires_at = current_time + timedelta(minutes=expires_minutes)
+        
+        # 生成并加密验证码
+        raw_code = self._generate_code()
+        self.code = self._hash_code(raw_code)
+        self._raw_code = raw_code  # 临时存储原始验证码用于发送邮件
+
+    @classmethod
+    def create_verification(cls, email, verification_type, user_id=None, expires_minutes=15):
+        """创建验证记录
+        
+        Args:
+            email: 邮箱地址
+            verification_type: 验证类型
+            user_id: 用户ID(可选)
+            expires_minutes: 过期时间(分钟)
+            
+        Returns:
+            EmailVerification: 验证记录实例
+        """
+        return cls(
+            email=email,
+            verification_type=verification_type,
+            user_id=user_id,
+            expires_minutes=expires_minutes
+        )
+
+    def _generate_code(self, length=6):
+        """生成随机验证码
+        
+        Args:
+            length: 验证码长度
+            
+        Returns:
+            str: 验证码
+        """
+        characters = string.digits
+        return ''.join(secrets.choice(characters) for _ in range(length))
+
+    def _hash_code(self, code):
+        """对验证码进行哈希加密
+        
+        Args:
+            code: 原始验证码
+            
+        Returns:
+            str: 加密后的验证码
+        """
+        return hashlib.sha256(code.encode()).hexdigest()
+
+    def verify(self, input_code):
+        """验证验证码
+        
+        Args:
+            input_code: 用户输入的验证码
+            
+        Returns:
+            bool: 验证是否成功
+        """
+        if self.is_verified:
+            return False
+            
+        if self.is_expired():
+            return False
+            
+        hashed_input = self._hash_code(input_code)
+        if hashed_input == self.code:
+            # 使用中国时区时间设置验证时间
+            china_tz = pytz.timezone('Asia/Shanghai')
+            self.verified_at = datetime.now(china_tz).replace(tzinfo=None)
+            self.is_verified = True
+            return True
+            
+        return False
+
+    def verify_hashed(self, hashed_code):
+        """验证已经加密的验证码
+        
+        Args:
+            hashed_code: 已经加密的验证码
+            
+        Returns:
+            bool: 验证是否成功
+        """
+        if self.is_verified:
+            return False
+            
+        if self.is_expired():
+            return False
+            
+        # 直接比较加密后的验证码
+        if hashed_code == self.code:
+            # 使用中国时区时间设置验证时间
+            china_tz = pytz.timezone('Asia/Shanghai')
+            self.verified_at = datetime.now(china_tz).replace(tzinfo=None)
+            self.is_verified = True
+            return True
+            
+        return False
+
+    def is_expired(self):
+        """检查是否已过期
+        
+        Returns:
+            bool: 是否已过期
+        """
+        # 使用中国时区时间进行比较
+        china_tz = pytz.timezone('Asia/Shanghai')
+        current_time = datetime.now(china_tz).replace(tzinfo=None)
+        return current_time > self.expires_at
+
+    def get_raw_code(self):
+        """获取原始验证码(仅在创建时可用)
+        
+        Returns:
+            str: 原始验证码
+        """
+        return getattr(self, '_raw_code', None)
+
+    def to_dict(self):
+        """转换为字典格式
+        
+        Returns:
+            dict: 对象字典表示
+        """
+        return {
+            'id': self.id,
+            'email': self.email,
+            'type': self.type,
+            'user_id': self.user_id,
+            'is_verified': self.is_verified,
+            'expires_at': self.expires_at.isoformat() if self.expires_at else None,
+            'created_at': self.created_at.isoformat() if self.created_at else None,
+            'verified_at': self.verified_at.isoformat() if self.verified_at else None
+        }
+
+    def __repr__(self):
+        return f"<EmailVerification(id={self.id}, email='{self.email}', type='{self.type}', verified={self.is_verified})>"
\ No newline at end of file
diff --git a/rhj/backend/app/models/users.py b/rhj/backend/app/models/users.py
new file mode 100644
index 0000000..8edc8be
--- /dev/null
+++ b/rhj/backend/app/models/users.py
@@ -0,0 +1,53 @@
+from . import Base
+from sqlalchemy import (
+    Column, Integer, String, Enum, TIMESTAMP, text
+)
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import relationship
+
+
+class User(Base):
+    __tablename__ = 'users'
+
+    def to_dict(self):
+        return {
+            'id': self.id,
+            'username': self.username if self.username else None,
+            'email': self.email if self.email else None,
+            'avatar': self.avatar if self.avatar else None,
+            'role': self.role if self.role else None,
+            'bio': self.bio if self.bio else None,
+            'status': self.status if self.status else None,
+            'created_at': self.created_at.isoformat() if self.created_at else None,
+            'updated_at': self.updated_at.isoformat() if self.updated_at else None
+        }
+
+    id = Column(Integer, primary_key=True, autoincrement=True, comment='用户ID')
+    username = Column(String(50), nullable=False, unique=True, comment='用户名')
+    password = Column(String(255), nullable=False, comment='加密密码')
+    email = Column(String(100), nullable=False, unique=True, comment='邮箱')
+    avatar = Column(String(255), comment='头像URL')
+    role = Column(Enum('user', 'admin', 'superadmin', name='user_role'), comment='角色')
+    bio = Column(String(255), comment='个人简介')
+    status = Column(
+        Enum('active','banned','muted', name='user_status'),
+        nullable=False,
+        server_default=text("'active'"),
+        comment='账号状态'
+    )
+    created_at = Column(
+        TIMESTAMP,
+        nullable=True,
+        server_default=text('CURRENT_TIMESTAMP'),
+        comment='创建时间'
+    )
+    updated_at = Column(
+        TIMESTAMP,
+        nullable=True,
+        server_default=text('CURRENT_TIMESTAMP'),
+        onupdate=text('CURRENT_TIMESTAMP'),
+        comment='更新时间'
+    )
+    
+    # 关联关系
+    email_verifications = relationship("EmailVerification", back_populates="user")