合并login

Change-Id: Ie06ed019cbb00d52e0b9e1f3c7a56c947b57a42c
diff --git a/Merge/back_rhj/.env.example b/Merge/back_rhj/.env.example
new file mode 100644
index 0000000..fc7fb37
--- /dev/null
+++ b/Merge/back_rhj/.env.example
@@ -0,0 +1,37 @@
+# 数据库配置
+SQLURL=mysql+pymysql://username:password@localhost:3306/redbook
+SQLPORT=3306
+SQLNAME=redbook
+SQLUSER=root
+SQLPWD=123456
+
+# JWT密钥
+JWT_SECRET_KEY=your-jwt-secret-key-here
+
+# Flask密钥
+SECRET_KEY=your-flask-secret-key-here
+
+# 邮件服务配置
+# QQ邮箱示例配置
+MAIL_SERVER=smtp.qq.com
+MAIL_PORT=587
+MAIL_USE_TLS=true
+MAIL_USERNAME=1650349938@qq.com
+MAIL_PASSWORD=
+MAIL_DEFAULT_SENDER=1650349938@qq.com
+
+# Gmail示例配置
+# MAIL_SERVER=smtp.gmail.com
+# MAIL_PORT=587
+# MAIL_USE_TLS=true
+# MAIL_USERNAME=your_email@gmail.com
+# MAIL_PASSWORD=your_app_password
+# MAIL_DEFAULT_SENDER=your_email@gmail.com
+
+# 163邮箱示例配置
+# MAIL_SERVER=smtp.163.com
+# MAIL_PORT=25
+# MAIL_USE_TLS=false
+# MAIL_USERNAME=your_email@163.com
+# MAIL_PASSWORD=your_email_password
+# MAIL_DEFAULT_SENDER=your_email@163.com
diff --git a/Merge/back_rhj/README.md b/Merge/back_rhj/README.md
new file mode 100644
index 0000000..f425dd3
--- /dev/null
+++ b/Merge/back_rhj/README.md
@@ -0,0 +1,99 @@
+# RHJ Backend API
+
+这是RHJ项目的后端API服务,提供用户认证和管理功能。
+
+## 功能特性
+
+- 用户注册/登录
+- JWT令牌认证
+- 密码加密存储
+- 用户信息管理
+- 跨域支持(CORS)
+
+## 安装运行
+
+### 1. 安装依赖
+
+```bash
+pip install -r requirements.txt
+```
+
+### 2. 配置环境变量
+
+复制 `.env.example` 为 `.env` 并配置数据库连接信息:
+
+```bash
+cp .env.example .env
+```
+
+编辑 `.env` 文件,配置你的数据库连接信息。
+
+### 3. 运行服务
+
+```bash
+python app.py
+```
+
+服务将在 `http://localhost:8081` 启动。
+
+## API接口
+
+### 用户认证
+
+#### 用户注册
+- **POST** `/register`
+- **参数:**
+  ```json
+  {
+    "username": "用户名",
+    "email": "邮箱",
+    "password": "密码"
+  }
+  ```
+
+#### 用户登录
+- **POST** `/login`
+- **参数:**
+  ```json
+  {
+    "username": "用户名或邮箱",
+    "password": "密码"
+  }
+  ```
+
+#### 获取用户信息
+- **GET** `/profile`
+- **Headers:** `Authorization: Bearer <token>`
+
+#### 用户登出
+- **POST** `/logout`
+- **Headers:** `Authorization: Bearer <token>`
+
+#### 健康检查
+- **GET** `/health`
+
+## 响应格式
+
+所有API返回统一的JSON格式:
+
+```json
+{
+  "success": true/false,
+  "message": "提示信息",
+  "data": "数据内容(可选)"
+}
+```
+
+## 数据库模型
+
+### users 表
+- id: 用户ID(主键)
+- username: 用户名(唯一)
+- password: 加密密码
+- email: 邮箱(唯一)
+- avatar: 头像URL
+- role: 角色(user/admin/superadmin)
+- bio: 个人简介
+- status: 账号状态(active/banned/muted)
+- created_at: 创建时间
+- updated_at: 更新时间
diff --git a/Merge/back_rhj/__pycache__/config.cpython-312.pyc b/Merge/back_rhj/__pycache__/config.cpython-312.pyc
new file mode 100644
index 0000000..59299dd
--- /dev/null
+++ b/Merge/back_rhj/__pycache__/config.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/__pycache__/test_smtp.cpython-312.pyc b/Merge/back_rhj/__pycache__/test_smtp.cpython-312.pyc
new file mode 100644
index 0000000..6d91369
--- /dev/null
+++ b/Merge/back_rhj/__pycache__/test_smtp.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app.py b/Merge/back_rhj/app.py
new file mode 100644
index 0000000..df7a598
--- /dev/null
+++ b/Merge/back_rhj/app.py
@@ -0,0 +1,6 @@
+from app import create_app
+
+app = create_app()
+
+if __name__ == "__main__":
+    app.run(debug=True,port=8082,host='0.0.0.0')
\ No newline at end of file
diff --git a/Merge/back_rhj/app/__init__.py b/Merge/back_rhj/app/__init__.py
new file mode 100644
index 0000000..c50a674
--- /dev/null
+++ b/Merge/back_rhj/app/__init__.py
@@ -0,0 +1,23 @@
+from flask import Flask
+from flask_cors import CORS
+
+def create_app():
+    app = Flask(__name__)
+    
+    # 启用CORS支持跨域请求
+    CORS(app)
+    
+    # Load configuration
+    app.config.from_object('config.Config')
+
+    # Register blueprints or routes
+    from .routes import main as main_blueprint
+    app.register_blueprint(main_blueprint)
+    
+    # Register recommendation blueprint
+    from .blueprints.recommend import recommend_bp
+    app.register_blueprint(recommend_bp)
+
+    return app
+
+app = create_app()
diff --git a/Merge/back_rhj/app/__pycache__/__init__.cpython-312.pyc b/Merge/back_rhj/app/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..7c7d017
--- /dev/null
+++ b/Merge/back_rhj/app/__pycache__/__init__.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/__pycache__/routes.cpython-312.pyc b/Merge/back_rhj/app/__pycache__/routes.cpython-312.pyc
new file mode 100644
index 0000000..0ec74bd
--- /dev/null
+++ b/Merge/back_rhj/app/__pycache__/routes.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/blueprints/__pycache__/__init__.cpython-312.pyc b/Merge/back_rhj/app/blueprints/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..1388273
--- /dev/null
+++ b/Merge/back_rhj/app/blueprints/__pycache__/__init__.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/blueprints/__pycache__/recommend.cpython-312.pyc b/Merge/back_rhj/app/blueprints/__pycache__/recommend.cpython-312.pyc
new file mode 100644
index 0000000..29c786d
--- /dev/null
+++ b/Merge/back_rhj/app/blueprints/__pycache__/recommend.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/blueprints/recommend.py b/Merge/back_rhj/app/blueprints/recommend.py
new file mode 100644
index 0000000..97b8908
--- /dev/null
+++ b/Merge/back_rhj/app/blueprints/recommend.py
@@ -0,0 +1,303 @@
+from flask import Blueprint, request, jsonify
+from app.services.recommendation_service import RecommendationService
+from app.functions.FAuth import FAuth
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+from config import Config
+from functools import wraps
+
+recommend_bp = Blueprint('recommend', __name__, url_prefix='/api/recommend')
+
+def token_required(f):
+    """装饰器:需要令牌验证"""
+    @wraps(f)
+    def decorated(*args, **kwargs):
+        token = request.headers.get('Authorization')
+        if not token:
+            return jsonify({'success': False, 'message': '缺少访问令牌'}), 401
+        
+        session = None
+        try:
+            # 移除Bearer前缀
+            if token.startswith('Bearer '):
+                token = token[7:]
+            
+            engine = create_engine(Config.SQLURL)
+            SessionLocal = sessionmaker(bind=engine)
+            session = SessionLocal()
+            f_auth = FAuth(session)
+            
+            user = f_auth.get_user_by_token(token)
+            if not user:
+                return jsonify({'success': False, 'message': '无效的访问令牌'}), 401
+            
+            # 将用户信息传递给路由函数
+            return f(user, *args, **kwargs)
+        except Exception as e:
+            if session:
+                session.rollback()
+            return jsonify({'success': False, 'message': '令牌验证失败'}), 401
+        finally:
+            if session:
+                session.close()
+    
+    return decorated
+
+# 初始化推荐服务
+recommendation_service = RecommendationService()
+
+@recommend_bp.route('/get_recommendations', methods=['POST'])
+@token_required
+def get_recommendations(current_user):
+    """获取个性化推荐"""
+    try:
+        data = request.get_json()
+        user_id = data.get('user_id') or current_user.user_id
+        topk = data.get('topk', 2)
+        
+        recommendations = recommendation_service.get_recommendations(user_id, topk)
+        
+        return jsonify({
+            'success': True,
+            'data': {
+                'user_id': user_id,
+                'recommendations': recommendations,
+                'count': len(recommendations)
+            },
+            'message': '推荐获取成功'
+        })
+    except Exception as e:
+        return jsonify({
+            'success': False, 
+            'message': f'推荐获取失败: {str(e)}'
+        }), 500
+
+@recommend_bp.route('/cold_start', methods=['GET'])
+def cold_start_recommendations():
+    """冷启动推荐(无需登录)"""
+    try:
+        topk = request.args.get('topk', 2, type=int)
+        
+        recommendations = recommendation_service.user_cold_start(topk)
+        
+        return jsonify({
+            'success': True,
+            'data': {
+                'recommendations': recommendations,
+                'count': len(recommendations),
+                'type': 'cold_start'
+            },
+            'message': '热门推荐获取成功'
+        })
+    except Exception as e:
+        return jsonify({
+            'success': False, 
+            'message': f'推荐获取失败: {str(e)}'
+        }), 500
+
+@recommend_bp.route('/health', methods=['GET'])
+def health_check():
+    """推荐系统健康检查"""
+    try:
+        # 简单的健康检查
+        import torch
+        cuda_available = torch.cuda.is_available()
+        
+        return jsonify({
+            'success': True,
+            'data': {
+                'status': 'healthy',
+                'cuda_available': cuda_available,
+                'device': 'cuda' if cuda_available else 'cpu'
+            },
+            'message': '推荐系统运行正常'
+        })
+    except Exception as e:
+        return jsonify({
+            'success': False,
+            'message': f'推荐系统异常: {str(e)}'
+        }), 500
+
+@recommend_bp.route('/multi_recall', methods=['POST'])
+@token_required
+def multi_recall_recommendations(current_user):
+    """多路召回推荐"""
+    try:
+        data = request.get_json()
+        user_id = data.get('user_id') or current_user.user_id
+        topk = data.get('topk', 2)
+        
+        # 强制使用多路召回
+        result = recommendation_service.run_inference(user_id, topk, use_multi_recall=True)
+        
+        # 如果是冷启动直接返回详细信息,否则查详情
+        if isinstance(result, list) and result and isinstance(result[0], dict):
+            recommendations = result
+        else:
+            # result 是 (topk_post_ids, topk_scores) 的元组
+            if isinstance(result, tuple) and len(result) == 2:
+                topk_post_ids, topk_scores = result
+                recommendations = recommendation_service.get_post_info(topk_post_ids, topk_scores)
+            else:
+                recommendations = recommendation_service.get_post_info(result)
+        
+        return jsonify({
+            'success': True,
+            'data': {
+                'user_id': user_id,
+                'recommendations': recommendations,
+                'count': len(recommendations),
+                'type': 'multi_recall'
+            },
+            'message': '多路召回推荐获取成功'
+        })
+    except Exception as e:
+        return jsonify({
+            'success': False, 
+            'message': f'多路召回推荐获取失败: {str(e)}'
+        }), 500
+
+@recommend_bp.route('/lightgcn', methods=['POST'])
+@token_required
+def lightgcn_recommendations(current_user):
+    """LightGCN推荐"""
+    try:
+        data = request.get_json()
+        user_id = data.get('user_id') or current_user.user_id
+        topk = data.get('topk', 2)
+        
+        # 强制使用LightGCN
+        result = recommendation_service.run_inference(user_id, topk, use_multi_recall=False)
+        
+        # 如果是冷启动直接返回详细信息,否则查详情
+        if isinstance(result, list) and result and isinstance(result[0], dict):
+            recommendations = result
+        else:
+            # result 是 (topk_post_ids, topk_scores) 的元组
+            if isinstance(result, tuple) and len(result) == 2:
+                topk_post_ids, topk_scores = result
+                recommendations = recommendation_service.get_post_info(topk_post_ids, topk_scores)
+            else:
+                recommendations = recommendation_service.get_post_info(result)
+        
+        return jsonify({
+            'success': True,
+            'data': {
+                'user_id': user_id,
+                'recommendations': recommendations,
+                'count': len(recommendations),
+                'type': 'lightgcn'
+            },
+            'message': 'LightGCN推荐获取成功'
+        })
+    except Exception as e:
+        return jsonify({
+            'success': False, 
+            'message': f'LightGCN推荐获取失败: {str(e)}'
+        }), 500
+
+@recommend_bp.route('/train_multi_recall', methods=['POST'])
+@token_required  
+def train_multi_recall(current_user):
+    """训练多路召回模型"""
+    try:
+        # 只有管理员才能训练模型
+        if not hasattr(current_user, 'is_admin') or not current_user.is_admin:
+            return jsonify({
+                'success': False,
+                'message': '需要管理员权限'
+            }), 403
+        
+        recommendation_service.train_multi_recall()
+        
+        return jsonify({
+            'success': True,
+            'message': '多路召回模型训练完成'
+        })
+    except Exception as e:
+        return jsonify({
+            'success': False,
+            'message': f'模型训练失败: {str(e)}'
+        }), 500
+
+@recommend_bp.route('/recall_config', methods=['GET'])
+@token_required
+def get_recall_config(current_user):
+    """获取多路召回配置"""
+    try:
+        config = recommendation_service.recall_config
+        return jsonify({
+            'success': True,
+            'data': {
+                'config': config,
+                'multi_recall_enabled': recommendation_service.multi_recall_enabled
+            },
+            'message': '配置获取成功'
+        })
+    except Exception as e:
+        return jsonify({
+            'success': False,
+            'message': f'配置获取失败: {str(e)}'
+        }), 500
+
+@recommend_bp.route('/recall_config', methods=['POST'])
+@token_required
+def update_recall_config(current_user):
+    """更新多路召回配置"""
+    try:
+        # 只有管理员才能更新配置
+        if not hasattr(current_user, 'is_admin') or not current_user.is_admin:
+            return jsonify({
+                'success': False,
+                'message': '需要管理员权限'
+            }), 403
+        
+        data = request.get_json()
+        new_config = data.get('config', {})
+        
+        # 更新多路召回启用状态
+        if 'multi_recall_enabled' in data:
+            recommendation_service.multi_recall_enabled = data['multi_recall_enabled']
+        
+        # 更新具体配置
+        if new_config:
+            recommendation_service.update_recall_config(new_config)
+        
+        return jsonify({
+            'success': True,
+            'data': {
+                'config': recommendation_service.recall_config,
+                'multi_recall_enabled': recommendation_service.multi_recall_enabled
+            },
+            'message': '配置更新成功'
+        })
+    except Exception as e:
+        return jsonify({
+            'success': False,
+            'message': f'配置更新失败: {str(e)}'
+        }), 500
+
+@recommend_bp.route('/recall_stats/<int:user_id>', methods=['GET'])
+@token_required
+def get_recall_stats(current_user, user_id):
+    """获取用户的召回统计信息"""
+    try:
+        # 只允许查看自己的统计或管理员查看
+        if current_user.user_id != user_id and (not hasattr(current_user, 'is_admin') or not current_user.is_admin):
+            return jsonify({
+                'success': False,
+                'message': '权限不足'
+            }), 403
+        
+        stats = recommendation_service.get_multi_recall_stats(user_id)
+        
+        return jsonify({
+            'success': True,
+            'data': stats,
+            'message': '统计信息获取成功'
+        })
+    except Exception as e:
+        return jsonify({
+            'success': False,
+            'message': f'统计信息获取失败: {str(e)}'
+        }), 500
diff --git a/Merge/back_rhj/app/functions/FAuth.py b/Merge/back_rhj/app/functions/FAuth.py
new file mode 100644
index 0000000..d6310a5
--- /dev/null
+++ b/Merge/back_rhj/app/functions/FAuth.py
@@ -0,0 +1,611 @@
+from ..models.users import User as users
+from ..models.email_verification import EmailVerification
+from sqlalchemy.orm import Session
+import hashlib
+import jwt
+import smtplib
+import pytz
+from email.mime.text import MIMEText
+from email.mime.multipart import MIMEMultipart
+from datetime import datetime, timedelta
+from config import Config
+
+class FAuth:
+    def __init__(self, session: Session):
+        self.session = session
+        return
+    
+    def hash_password(self, password):
+        """密码加密"""
+        return hashlib.sha256(password.encode()).hexdigest()
+    
+    def is_password_hashed(self, password):
+        """检查密码是否已经被哈希加密
+        
+        Args:
+            password: 密码字符串
+            
+        Returns:
+            bool: 是否为已加密的密码(64位十六进制字符串)
+        """
+        import re
+        if not password or not isinstance(password, str):
+            return False
+        # SHA256 加密后是64位十六进制字符串
+        return bool(re.match(r'^[a-f0-9]{64}$', password, re.IGNORECASE))
+    
+    def safe_hash_password(self, password):
+        """安全的密码加密函数,避免重复加密
+        
+        Args:
+            password: 密码字符串
+            
+        Returns:
+            str: 加密后的密码
+        """
+        if not password:
+            raise ValueError('密码不能为空')
+        
+        # 如果已经是加密的密码,直接返回
+        if self.is_password_hashed(password):
+            return password
+        
+        # 否则进行加密
+        return self.hash_password(password)
+    
+    def verify_password(self, password, hashed_password):
+        """验证密码"""
+        return self.hash_password(password) == hashed_password
+    
+    def generate_token(self, user_id, role='user'):
+        """生成JWT令牌"""
+        payload = {
+            'user_id': user_id,
+            'role': role,
+            'exp': datetime.utcnow() + timedelta(hours=24)  # 24小时过期
+        }
+        return jwt.encode(payload, Config.JWT_SECRET_KEY, algorithm='HS256')
+    
+    def verify_token(self, token):
+        """验证JWT令牌"""
+        try:
+            payload = jwt.decode(token, Config.JWT_SECRET_KEY, algorithms=['HS256'])
+            return {
+                'user_id': payload['user_id'],
+                'role': payload.get('role', 'user')  # 默认角色为user,兼容旧令牌
+            }
+        except jwt.ExpiredSignatureError:
+            return None
+        except jwt.InvalidTokenError:
+            return None
+    
+    def login(self, username_or_email, password):
+        """用户登录"""
+        try:
+            # 查找用户
+            user = self.session.query(users).filter(
+                (users.email == username_or_email)
+            ).first()
+            
+            if not user:
+                return {'success': False, 'message': '用户不存在'}
+            
+            # 检查账号状态
+            if user.status != 'active':
+                return {'success': False, 'message': '账号已被禁用'}
+            
+            # 验证密码(前端已加密,后端使用安全比较)
+            if not self.safe_hash_password(password) == user.password:
+                return {'success': False, 'message': '密码错误'}
+            
+            # 生成令牌
+            token = self.generate_token(user.id, user.role)
+            
+            return {
+                'success': True, 
+                'message': '登录成功',
+                'token': token,
+                'user': user.to_dict()
+            }
+        except Exception as e:
+            print(f"Login error: {str(e)}")
+            return {'success': False, 'message': f'登录失败: {str(e)}'}
+    
+    def register(self, username, email, password, verification_code):
+        """用户注册"""
+        # 检查用户名是否存在
+        existing_user = self.session.query(users).filter(
+            (users.username == username) | (users.email == email)
+        ).first()
+        
+        if existing_user:
+            if existing_user.username == username:
+                return {'success': False, 'message': '用户名已存在'}
+            else:
+                return {'success': False, 'message': '邮箱已被注册'}
+            
+        verification = self.session.query(EmailVerification).filter(
+            EmailVerification.email == email,
+            EmailVerification.type == 'register',
+            EmailVerification.is_verified == False
+        ).order_by(EmailVerification.created_at.desc()).first()
+        
+        if not verification:
+            return {
+                'success': False,
+                'message': '验证码不存在或已过期'
+            }
+
+        # 验证验证码(检查是否为已加密的验证码)
+        verification_success = False
+        if self.is_password_hashed(verification_code):
+            # 如果是已加密的验证码,直接比较
+            verification_success = verification.verify_hashed(verification_code)
+        else:
+            # 如果是明文验证码,先加密再比较
+            verification_success = verification.verify(verification_code)
+        if not verification_success:
+            return {
+                'success': False,
+                'message': '验证码错误或已过期'
+            }
+        # 如果验证码验证成功,标记为已验证
+        verification.is_verified = True
+        verification.verified_at = datetime.now(pytz.timezone('Asia/Shanghai')).replace(tzinfo=None)
+            
+        
+        # 创建新用户(使用安全加密函数避免重复加密)
+        hashed_password = self.safe_hash_password(password)
+        new_user = users(
+            username=username,
+            email=email,
+            password=hashed_password,
+            role='user',
+            status='active'
+        )
+        
+        try:
+            self.session.add(new_user)
+            self.session.commit()
+            
+            # 生成令牌
+            token = self.generate_token(new_user.id, new_user.role)
+            
+            return {
+                'success': True,
+                'message': '注册成功',
+                'token': token,
+                'user': new_user.to_dict()
+            }
+        except Exception as e:
+            self.session.rollback()
+            return {'success': False, 'message': '注册失败,请稍后重试'}
+    
+    def get_user_by_token(self, token):
+        """通过令牌获取用户信息"""
+        token_data = self.verify_token(token)
+        if not token_data:
+            return None
+        
+        user_id = token_data['user_id'] if isinstance(token_data, dict) else token_data
+        user = self.session.query(users).filter(users.id == user_id).first()
+        return user
+    
+    def send_verification_email(self, email, verification_type='register', user_id=None):
+        """发送邮箱验证码
+        
+        Args:
+            email: 目标邮箱地址
+            verification_type: 验证类型 ('register', 'reset_password', 'email_change')
+            user_id: 用户ID(可选)
+            
+        Returns:
+            dict: 发送结果
+        """
+        try:
+            # 检查邮件配置
+            if not all([Config.MAIL_USERNAME, Config.MAIL_PASSWORD, Config.MAIL_DEFAULT_SENDER]):
+                return {
+                    'success': False, 
+                    'message': '邮件服务配置不完整,请联系管理员'
+                }
+                
+            if verification_type not in ['register', 'reset_password', 'email_change']:
+                return {
+                    'success': False,
+                    'message': '无效的验证类型'
+                }
+                
+            if verification_type == 'reset_password' or verification_type == 'email_change':
+                # 检查用户是否存在
+                user = self.session.query(users).filter(users.email == email).first()
+                if not user:
+                    return {
+                        'success': False,
+                        'message': '用户不存在或邮箱不匹配'
+                    }
+            elif verification_type == 'register':
+                # 检查邮箱是否已注册
+                existing_user = self.session.query(users).filter(users.email == email).first()
+                if existing_user:
+                    return {
+                        'success': False,
+                        'message': '邮箱已被注册'
+                    }
+            
+            # 创建验证记录
+            verification = EmailVerification.create_verification(
+                email=email,
+                verification_type=verification_type,
+                user_id=user_id,
+                expires_minutes=15  # 15分钟过期
+            )
+            
+            # 保存到数据库
+            self.session.add(verification)
+            
+            # 获取验证码
+            verification_code = verification.get_raw_code()
+            if not verification_code:
+                return {
+                    'success': False,
+                    'message': '验证码生成失败'
+                }
+            
+            # 发送邮件
+            result = self._send_email(email, verification_code, verification_type)
+            
+            if result['success']:
+                return {
+                    'success': True,
+                    'message': '验证码已发送到您的邮箱',
+                    'verification_id': verification.id
+                }
+            else:
+                # 如果邮件发送失败,删除验证记录
+                self.session.delete(verification)
+                self.session.commit()
+                return result
+                
+        except Exception as e:
+            self.session.rollback()
+            print(f"Send verification email error: {str(e)}")
+            return {
+                'success': False,
+                'message': f'发送验证码失败: {str(e)}'
+            }
+    
+    def _send_email(self, to_email, verification_code, verification_type):
+        """发送邮件的具体实现
+        
+        Args:
+            to_email: 收件人邮箱
+            verification_code: 验证码
+            verification_type: 验证类型
+            
+        Returns:
+            dict: 发送结果
+        """
+        try:
+            # 根据验证类型设置邮件内容
+            subject_map = {
+                'register': '注册验证码',
+                'reset_password': '密码重置验证码',
+                'email_change': '邮箱变更验证码'
+            }
+            
+            message_map = {
+                'register': '欢迎注册我们的平台!',
+                'reset_password': '您正在重置密码',
+                'email_change': '您正在变更邮箱地址'
+            }
+            
+            subject = subject_map.get(verification_type, '验证码')
+            message_intro = message_map.get(verification_type, '验证码')
+            
+            # 创建邮件内容
+            msg = MIMEMultipart('alternative')
+            msg['Subject'] = subject
+            msg['From'] = Config.MAIL_DEFAULT_SENDER
+            msg['To'] = to_email
+            
+            # HTML邮件内容
+            html_body = f"""
+            <!DOCTYPE html>
+            <html>
+            <head>
+                <meta charset="utf-8">
+                <title>{subject}</title>
+            </head>
+            <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
+                <div style="max-width: 600px; margin: 0 auto; padding: 20px;">
+                    <div style="background-color: #f8f9fa; padding: 20px; border-radius: 8px; margin-bottom: 20px;">
+                        <h2 style="color: #007bff; margin-top: 0;">{message_intro}</h2>
+                        <p>您的验证码是:</p>
+                        <div style="background-color: #007bff; color: white; padding: 15px; border-radius: 5px; text-align: center; font-size: 24px; font-weight: bold; letter-spacing: 3px; margin: 20px 0;">
+                            {verification_code}
+                        </div>
+                        <p style="color: #666; font-size: 14px;">
+                            • 验证码有效期为15分钟<br>
+                            • 请勿将验证码透露给他人<br>
+                            • 如果这不是您的操作,请忽略此邮件
+                        </p>
+                    </div>
+                    <div style="text-align: center; color: #999; font-size: 12px; border-top: 1px solid #eee; padding-top: 20px;">
+                        <p>此邮件由系统自动发送,请勿回复</p>
+                    </div>
+                </div>
+            </body>
+            </html>
+            """
+            
+            # 纯文本内容(备用)
+            text_body = f"""
+            {message_intro}
+            
+            您的验证码是:{verification_code}
+            
+            验证码有效期为15分钟
+            请勿将验证码透露给他人
+            如果这不是您的操作,请忽略此邮件
+            
+            此邮件由系统自动发送,请勿回复
+            """
+            
+            # 添加邮件内容
+            text_part = MIMEText(text_body, 'plain', 'utf-8')
+            html_part = MIMEText(html_body, 'html', 'utf-8')
+            
+            msg.attach(text_part)
+            msg.attach(html_part)
+            
+            # 连接SMTP服务器并发送邮件
+            server = None
+            try:
+                server = smtplib.SMTP(Config.MAIL_SERVER, Config.MAIL_PORT)
+                if Config.MAIL_USE_TLS:
+                    server.starttls()
+                
+                server.login(Config.MAIL_USERNAME, Config.MAIL_PASSWORD)
+                result = server.send_message(msg)
+                
+                # 检查发送结果
+                if result:
+                    # 如果有失败的收件人,记录日志
+                    print(f"邮件发送部分失败: {result}")
+                
+                return {
+                    'success': True,
+                    'message': '邮件发送成功'
+                }
+            finally:
+                # 确保连接被正确关闭
+                if server:
+                    try:
+                        server.quit()
+                    except Exception:
+                        # 如果quit()失败,强制关闭连接
+                        try:
+                            server.close()
+                        except Exception:
+                            pass
+            
+        except smtplib.SMTPAuthenticationError:
+            return {
+                'success': False,
+                'message': '邮件服务认证失败,请检查邮箱配置'
+            }
+        except smtplib.SMTPException as e:
+            return {
+                'success': False,
+                'message': f'邮件发送失败: {str(e)}'
+            }
+        except Exception as e:
+            return {
+                'success': False,
+                'message': f'发送邮件时发生错误: {str(e)}'
+            }
+    
+    def verify_email_code(self, email, code, verification_type='register'):
+        """验证邮箱验证码
+        
+        Args:
+            email: 邮箱地址
+            code: 验证码
+            verification_type: 验证类型
+            
+        Returns:
+            dict: 验证结果
+        """
+        try:
+            # 查找最新的未验证的验证记录
+            verification = self.session.query(EmailVerification).filter(
+                EmailVerification.email == email,
+                EmailVerification.type == verification_type,
+                EmailVerification.is_verified == False
+            ).order_by(EmailVerification.created_at.desc()).first()
+            
+            if not verification:
+                return {
+                    'success': False,
+                    'message': '验证码不存在或已过期'
+                }
+            
+            # 验证验证码(检查是否为已加密的验证码)
+            verification_success = False
+            if self.is_password_hashed(code):
+                # 如果是已加密的验证码,直接比较
+                verification_success = verification.verify_hashed(code)
+            else:
+                # 如果是明文验证码,先加密再比较
+                verification_success = verification.verify(code)
+            
+            if verification_success:
+                # 不在这里提交事务,留给调用者决定何时提交
+                # self.session.commit()  # 注释掉立即提交
+                return {
+                    'success': True,
+                    'message': '验证成功',
+                    'verification_id': verification.id
+                }
+            else:
+                return {
+                    'success': False,
+                    'message': '验证码错误或已过期'
+                }
+                
+        except Exception as e:
+            print(f"Verify email code error: {str(e)}")
+            return {
+                'success': False,
+                'message': f'验证失败: {str(e)}'
+            }
+    
+    def reset_password(self, email, new_password, verification_code):
+        """重置用户密码
+        
+        Args:
+            email: 用户邮箱
+            new_password: 新密码
+            verification_code: 验证码
+            
+        Returns:
+            dict: 重置结果
+        """
+        try:
+            # 检查是否有最近已验证的重置密码验证记录(5分钟内)
+            
+            china_tz = pytz.timezone('Asia/Shanghai')
+            current_time = datetime.now(china_tz).replace(tzinfo=None)
+            five_minutes_ago = current_time - timedelta(minutes=5)
+            
+            # 查找最近5分钟内已验证的重置密码验证记录
+            recent_verification = self.session.query(EmailVerification).filter(
+                EmailVerification.email == email,
+                EmailVerification.type == 'reset_password',
+                EmailVerification.is_verified == True,
+                EmailVerification.verified_at >= five_minutes_ago
+            ).order_by(EmailVerification.verified_at.desc()).first()
+            
+            if not recent_verification:
+                return {
+                    'success': False,
+                    'message': '验证码未验证或已过期,请重新验证'
+                }
+            
+            # 查找用户
+            user = self.session.query(users).filter(users.email == email).first()
+            if not user:
+                return {
+                    'success': False,
+                    'message': '用户不存在'
+                }
+            
+            # 检查账号状态
+            if user.status != 'active':
+                return {
+                    'success': False,
+                    'message': '账号已被禁用,无法重置密码'
+                }
+            
+            # 更新密码(使用安全加密函数避免重复加密)
+            user.password = self.safe_hash_password(new_password)
+            # 使用中国时区时间
+            china_tz = pytz.timezone('Asia/Shanghai')
+            user.updated_at = datetime.now(china_tz).replace(tzinfo=None)
+            
+            # 提交更改
+            self.session.commit()
+            
+            return {
+                'success': True,
+                'message': '密码重置成功'
+            }
+            
+        except Exception as e:
+            self.session.rollback()
+            print(f"Reset password error: {str(e)}")
+            return {
+                'success': False,
+                'message': f'密码重置失败: {str(e)}'
+            }
+    
+    def reset_password_with_verification(self, email, new_password, verification_code):
+        """重置用户密码(一步完成验证码验证和密码重置)
+        
+        Args:
+            email: 用户邮箱
+            new_password: 新密码
+            verification_code: 验证码
+            
+        Returns:
+            dict: 重置结果
+        """
+        try:
+            # 查找用户
+            user = self.session.query(users).filter(users.email == email).first()
+            if not user:
+                return {
+                    'success': False,
+                    'message': '用户不存在'
+                }
+            
+            # 检查账号状态
+            if user.status != 'active':
+                return {
+                    'success': False,
+                    'message': '账号已被禁用,无法重置密码'
+                }
+            
+            # 验证验证码
+            verification = self.session.query(EmailVerification).filter(
+                EmailVerification.email == email,
+                EmailVerification.type == 'reset_password',
+                EmailVerification.is_verified == False
+            ).order_by(EmailVerification.created_at.desc()).first()
+            
+            if not verification:
+                return {
+                    'success': False,
+                    'message': '验证码不存在或已过期'
+                }
+
+            # 验证验证码(检查是否为已加密的验证码)
+            verification_success = False
+            if self.is_password_hashed(verification_code):
+                # 如果是已加密的验证码,直接比较
+                verification_success = verification.verify_hashed(verification_code)
+            else:
+                # 如果是明文验证码,先加密再比较
+                verification_success = verification.verify(verification_code)
+            if not verification_success:
+                return {
+                    'success': False,
+                    'message': '验证码错误或已过期'
+                }
+            # 如果验证码验证成功,标记为已验证
+            verification.is_verified = True
+            verification.verified_at = datetime.now(pytz.timezone('Asia/Shanghai')).replace(tzinfo=None)
+            
+            # 更新密码(使用安全加密函数避免重复加密)
+            user.password = self.safe_hash_password(new_password)
+            
+            # 使用中国时区时间更新时间戳
+            china_tz = pytz.timezone('Asia/Shanghai')
+            user.updated_at = datetime.now(china_tz).replace(tzinfo=None)
+            
+            # 提交更改
+            self.session.commit()
+            
+            return {
+                'success': True,
+                'message': '密码重置成功'
+            }
+            
+        except Exception as e:
+            self.session.rollback()
+            print(f"Reset password with verification error: {str(e)}")
+            return {
+                'success': False,
+                'message': f'密码重置失败: {str(e)}'
+            }
diff --git a/Merge/back_rhj/app/functions/__pycache__/FAuth.cpython-312.pyc b/Merge/back_rhj/app/functions/__pycache__/FAuth.cpython-312.pyc
new file mode 100644
index 0000000..49086a6
--- /dev/null
+++ b/Merge/back_rhj/app/functions/__pycache__/FAuth.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/__init__.py b/Merge/back_rhj/app/models/__init__.py
new file mode 100644
index 0000000..179ba58
--- /dev/null
+++ b/Merge/back_rhj/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/Merge/back_rhj/app/models/__pycache__/__init__.cpython-312.pyc b/Merge/back_rhj/app/models/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..a0814d8
--- /dev/null
+++ b/Merge/back_rhj/app/models/__pycache__/__init__.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/__pycache__/email_verification.cpython-312.pyc b/Merge/back_rhj/app/models/__pycache__/email_verification.cpython-312.pyc
new file mode 100644
index 0000000..c1d6dfa
--- /dev/null
+++ b/Merge/back_rhj/app/models/__pycache__/email_verification.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/__pycache__/users.cpython-312.pyc b/Merge/back_rhj/app/models/__pycache__/users.cpython-312.pyc
new file mode 100644
index 0000000..58af35e
--- /dev/null
+++ b/Merge/back_rhj/app/models/__pycache__/users.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/email_verification.py b/Merge/back_rhj/app/models/email_verification.py
new file mode 100644
index 0000000..2d3f7da
--- /dev/null
+++ b/Merge/back_rhj/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/Merge/back_rhj/app/models/recall/__init__.py b/Merge/back_rhj/app/models/recall/__init__.py
new file mode 100644
index 0000000..98d926b
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/__init__.py
@@ -0,0 +1,24 @@
+"""
+多路召回模块
+
+包含以下召回算法:
+- SwingRecall: Swing召回算法,基于物品相似度
+- HotRecall: 热度召回算法,基于物品热度
+- AdRecall: 广告召回算法,专门处理广告内容
+- UserCFRecall: 用户协同过滤召回算法
+- MultiRecallManager: 多路召回管理器,整合所有召回策略
+"""
+
+from .swing_recall import SwingRecall
+from .hot_recall import HotRecall
+from .ad_recall import AdRecall
+from .usercf_recall import UserCFRecall
+from .multi_recall_manager import MultiRecallManager
+
+__all__ = [
+    'SwingRecall',
+    'HotRecall', 
+    'AdRecall',
+    'UserCFRecall',
+    'MultiRecallManager'
+]
diff --git a/Merge/back_rhj/app/models/recall/__pycache__/__init__.cpython-312.pyc b/Merge/back_rhj/app/models/recall/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..d1cf37c
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/__pycache__/__init__.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/recall/__pycache__/ad_recall.cpython-312.pyc b/Merge/back_rhj/app/models/recall/__pycache__/ad_recall.cpython-312.pyc
new file mode 100644
index 0000000..08a722c
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/__pycache__/ad_recall.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/recall/__pycache__/bloom_filter.cpython-312.pyc b/Merge/back_rhj/app/models/recall/__pycache__/bloom_filter.cpython-312.pyc
new file mode 100644
index 0000000..c4dae7e
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/__pycache__/bloom_filter.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/recall/__pycache__/hot_recall.cpython-312.pyc b/Merge/back_rhj/app/models/recall/__pycache__/hot_recall.cpython-312.pyc
new file mode 100644
index 0000000..cb6c725
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/__pycache__/hot_recall.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/recall/__pycache__/multi_recall_manager.cpython-312.pyc b/Merge/back_rhj/app/models/recall/__pycache__/multi_recall_manager.cpython-312.pyc
new file mode 100644
index 0000000..9a95456
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/__pycache__/multi_recall_manager.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/recall/__pycache__/swing_recall.cpython-312.pyc b/Merge/back_rhj/app/models/recall/__pycache__/swing_recall.cpython-312.pyc
new file mode 100644
index 0000000..d913d68
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/__pycache__/swing_recall.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/recall/__pycache__/usercf_recall.cpython-312.pyc b/Merge/back_rhj/app/models/recall/__pycache__/usercf_recall.cpython-312.pyc
new file mode 100644
index 0000000..adb6177
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/__pycache__/usercf_recall.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/recall/ad_recall.py b/Merge/back_rhj/app/models/recall/ad_recall.py
new file mode 100644
index 0000000..0fe3b0a
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/ad_recall.py
@@ -0,0 +1,207 @@
+import pymysql
+from typing import List, Tuple, Dict
+import random
+
+class AdRecall:
+    """
+    广告召回算法实现
+    专门用于召回广告类型的内容
+    """
+    
+    def __init__(self, db_config: dict):
+        """
+        初始化广告召回模型
+        
+        Args:
+            db_config: 数据库配置
+        """
+        self.db_config = db_config
+        self.ad_items = []
+        
+    def _get_ad_items(self):
+        """获取广告物品列表"""
+        conn = pymysql.connect(**self.db_config)
+        try:
+            cursor = conn.cursor()
+            
+            # 获取所有广告帖子,按热度和发布时间排序
+            cursor.execute("""
+                SELECT 
+                    p.id,
+                    p.heat,
+                    p.created_at,
+                    COUNT(DISTINCT b.user_id) as interaction_count,
+                    DATEDIFF(NOW(), p.created_at) as days_since_created
+                FROM posts p
+                LEFT JOIN behaviors b ON p.id = b.post_id
+                WHERE p.is_advertisement = 1 AND p.status = 'published'
+                GROUP BY p.id, p.heat, p.created_at
+                ORDER BY p.heat DESC, p.created_at DESC
+            """)
+            
+            results = cursor.fetchall()
+            
+            # 计算广告分数
+            items_with_scores = []
+            for row in results:
+                post_id, heat, created_at, interaction_count, days_since_created = row
+                
+                # 处理None值
+                heat = heat or 0
+                interaction_count = interaction_count or 0
+                days_since_created = days_since_created or 0
+                
+                # 广告分数计算:热度 + 交互数 - 时间惩罚
+                # 新发布的广告给予更高权重
+                freshness_bonus = max(0, 30 - days_since_created) / 30.0  # 30天内的新鲜度奖励
+                
+                ad_score = (
+                    heat * 0.6 +
+                    interaction_count * 0.3 +
+                    freshness_bonus * 100  # 新鲜度奖励
+                )
+                
+                items_with_scores.append((post_id, ad_score))
+            
+            # 按广告分数排序
+            self.ad_items = sorted(items_with_scores, key=lambda x: x[1], reverse=True)
+            
+        finally:
+            cursor.close()
+            conn.close()
+    
+    def train(self):
+        """训练广告召回模型"""
+        print("开始获取广告物品...")
+        self._get_ad_items()
+        print(f"广告召回模型训练完成,共{len(self.ad_items)}个广告物品")
+    
+    def recall(self, user_id: int, num_items: int = 10) -> List[Tuple[int, float]]:
+        """
+        为用户召回广告物品
+        
+        Args:
+            user_id: 用户ID
+            num_items: 召回物品数量
+            
+        Returns:
+            List of (item_id, score) tuples
+        """
+        # 如果尚未训练,先进行训练
+        if not hasattr(self, 'ad_items') or not self.ad_items:
+            self.train()
+        
+        # 获取用户已交互的广告,避免重复推荐
+        conn = pymysql.connect(**self.db_config)
+        try:
+            cursor = conn.cursor()
+            cursor.execute("""
+                SELECT DISTINCT b.post_id 
+                FROM behaviors b
+                JOIN posts p ON b.post_id = p.id
+                WHERE b.user_id = %s AND p.is_advertisement = 1
+                AND b.type IN ('like', 'favorite', 'comment', 'view')
+            """, (user_id,))
+            
+            user_interacted_ads = set(row[0] for row in cursor.fetchall())
+            
+            # 获取用户的兴趣标签(基于历史行为)
+            cursor.execute("""
+                SELECT t.name, COUNT(*) as count
+                FROM behaviors b
+                JOIN posts p ON b.post_id = p.id
+                JOIN post_tags pt ON p.id = pt.post_id
+                JOIN tags t ON pt.tag_id = t.id
+                WHERE b.user_id = %s AND b.type IN ('like', 'favorite', 'comment')
+                GROUP BY t.name
+                ORDER BY count DESC
+                LIMIT 10
+            """, (user_id,))
+            
+            user_interest_tags = set(row[0] for row in cursor.fetchall())
+            
+        finally:
+            cursor.close()
+            conn.close()
+        
+        # 过滤掉用户已交互的广告
+        filtered_ads = [
+            (item_id, score) for item_id, score in self.ad_items
+            if item_id not in user_interacted_ads
+        ]
+        
+        # 如果没有未交互的广告,但有广告数据,返回评分最高的广告(可能用户会再次感兴趣)
+        if not filtered_ads and self.ad_items:
+            print(f"用户 {user_id} 已与所有广告交互,返回评分最高的广告")
+            filtered_ads = self.ad_items[:num_items]
+        
+        # 如果用户有兴趣标签,可以进一步个性化广告推荐
+        if user_interest_tags and filtered_ads:
+            filtered_ads = self._personalize_ads(filtered_ads, user_interest_tags)
+        
+        return filtered_ads[:num_items]
+    
+    def _personalize_ads(self, ad_list: List[Tuple[int, float]], user_interest_tags: set) -> List[Tuple[int, float]]:
+        """
+        根据用户兴趣标签个性化广告推荐
+        
+        Args:
+            ad_list: 广告列表
+            user_interest_tags: 用户兴趣标签
+            
+        Returns:
+            个性化后的广告列表
+        """
+        conn = pymysql.connect(**self.db_config)
+        try:
+            cursor = conn.cursor()
+            
+            personalized_ads = []
+            for ad_id, ad_score in ad_list:
+                # 获取广告的标签
+                cursor.execute("""
+                    SELECT t.name
+                    FROM post_tags pt
+                    JOIN tags t ON pt.tag_id = t.id
+                    WHERE pt.post_id = %s
+                """, (ad_id,))
+                
+                ad_tags = set(row[0] for row in cursor.fetchall())
+                
+                # 计算标签匹配度
+                tag_match_score = len(ad_tags & user_interest_tags) / max(len(user_interest_tags), 1)
+                
+                # 调整广告分数
+                final_score = ad_score * (1 + tag_match_score)
+                personalized_ads.append((ad_id, final_score))
+            
+            # 重新排序
+            personalized_ads.sort(key=lambda x: x[1], reverse=True)
+            return personalized_ads
+            
+        finally:
+            cursor.close()
+            conn.close()
+    
+    def get_random_ads(self, num_items: int = 5) -> List[Tuple[int, float]]:
+        """
+        获取随机广告(用于多样性)
+        
+        Args:
+            num_items: 返回物品数量
+            
+        Returns:
+            List of (item_id, score) tuples
+        """
+        if len(self.ad_items) <= num_items:
+            return self.ad_items
+        
+        # 随机选择但倾向于高分广告
+        weights = [score for _, score in self.ad_items]
+        selected_indices = random.choices(
+            range(len(self.ad_items)), 
+            weights=weights, 
+            k=num_items
+        )
+        
+        return [self.ad_items[i] for i in selected_indices]
diff --git a/Merge/back_rhj/app/models/recall/bloom_filter.py b/Merge/back_rhj/app/models/recall/bloom_filter.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/bloom_filter.py
diff --git a/Merge/back_rhj/app/models/recall/hot_recall.py b/Merge/back_rhj/app/models/recall/hot_recall.py
new file mode 100644
index 0000000..dbc716c
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/hot_recall.py
@@ -0,0 +1,163 @@
+import pymysql
+from typing import List, Tuple, Dict
+import numpy as np
+
+class HotRecall:
+    """
+    热度召回算法实现
+    基于物品的热度(热度分数、交互次数等)进行召回
+    """
+    
+    def __init__(self, db_config: dict):
+        """
+        初始化热度召回模型
+        
+        Args:
+            db_config: 数据库配置
+        """
+        self.db_config = db_config
+        self.hot_items = []
+        
+    def _calculate_heat_scores(self):
+        """计算物品热度分数"""
+        conn = pymysql.connect(**self.db_config)
+        try:
+            cursor = conn.cursor()
+            
+            # 综合考虑多个热度指标
+            cursor.execute("""
+                SELECT 
+                    p.id,
+                    p.heat,
+                    COUNT(DISTINCT CASE WHEN b.type = 'like' THEN b.user_id END) as like_count,
+                    COUNT(DISTINCT CASE WHEN b.type = 'favorite' THEN b.user_id END) as favorite_count,
+                    COUNT(DISTINCT CASE WHEN b.type = 'comment' THEN b.user_id END) as comment_count,
+                    COUNT(DISTINCT CASE WHEN b.type = 'view' THEN b.user_id END) as view_count,
+                    COUNT(DISTINCT CASE WHEN b.type = 'share' THEN b.user_id END) as share_count,
+                    DATEDIFF(NOW(), p.created_at) as days_since_created
+                FROM posts p
+                LEFT JOIN behaviors b ON p.id = b.post_id
+                WHERE p.status = 'published'
+                GROUP BY p.id, p.heat, p.created_at
+            """)
+            
+            results = cursor.fetchall()
+            
+            # 计算综合热度分数
+            items_with_scores = []
+            for row in results:
+                post_id, heat, like_count, favorite_count, comment_count, view_count, share_count, days_since_created = row
+                
+                # 处理None值
+                heat = heat or 0
+                like_count = like_count or 0
+                favorite_count = favorite_count or 0
+                comment_count = comment_count or 0
+                view_count = view_count or 0
+                share_count = share_count or 0
+                days_since_created = days_since_created or 0
+                
+                # 综合热度分数计算
+                # 基础热度 + 加权的用户行为 + 时间衰减
+                behavior_score = (
+                    like_count * 1.0 +
+                    favorite_count * 2.0 +
+                    comment_count * 3.0 +
+                    view_count * 0.1 +
+                    share_count * 5.0
+                )
+                
+                # 时间衰减因子(越新的内容热度越高)
+                time_decay = np.exp(-days_since_created / 30.0)  # 30天半衰期
+                
+                # 最终热度分数
+                final_score = (heat * 0.3 + behavior_score * 0.7) * time_decay
+                
+                items_with_scores.append((post_id, final_score))
+            
+            # 按热度排序
+            self.hot_items = sorted(items_with_scores, key=lambda x: x[1], reverse=True)
+            
+        finally:
+            cursor.close()
+            conn.close()
+    
+    def train(self):
+        """训练热度召回模型"""
+        print("开始计算热度分数...")
+        self._calculate_heat_scores()
+        print(f"热度召回模型训练完成,共{len(self.hot_items)}个物品")
+    
+    def recall(self, user_id: int, num_items: int = 50) -> List[Tuple[int, float]]:
+        """
+        为用户召回热门物品
+        
+        Args:
+            user_id: 用户ID
+            num_items: 召回物品数量
+            
+        Returns:
+            List of (item_id, score) tuples
+        """
+        # 如果尚未训练,先进行训练
+        if not hasattr(self, 'hot_items') or not self.hot_items:
+            self.train()
+        
+        # 获取用户已交互的物品,避免重复推荐
+        conn = pymysql.connect(**self.db_config)
+        try:
+            cursor = conn.cursor()
+            cursor.execute("""
+                SELECT DISTINCT post_id 
+                FROM behaviors 
+                WHERE user_id = %s AND type IN ('like', 'favorite', 'comment')
+            """, (user_id,))
+            
+            user_interacted_items = set(row[0] for row in cursor.fetchall())
+            
+        finally:
+            cursor.close()
+            conn.close()
+        
+        # 过滤掉用户已交互的物品
+        filtered_items = [
+            (item_id, score) for item_id, score in self.hot_items
+            if item_id not in user_interacted_items
+        ]
+        
+        # 如果过滤后没有足够的候选,放宽条件:只过滤强交互(like, favorite, comment)
+        if len(filtered_items) < num_items:
+            print(f"热度召回:过滤后候选不足({len(filtered_items)}),放宽过滤条件")
+            conn = pymysql.connect(**self.db_config)
+            try:
+                cursor = conn.cursor()
+                cursor.execute("""
+                    SELECT DISTINCT post_id 
+                    FROM behaviors 
+                    WHERE user_id = %s AND type IN ('like', 'favorite', 'comment')
+                """, (user_id,))
+                
+                strong_interacted_items = set(row[0] for row in cursor.fetchall())
+                
+            finally:
+                cursor.close()
+                conn.close()
+            
+            filtered_items = [
+                (item_id, score) for item_id, score in self.hot_items
+                if item_id not in strong_interacted_items
+            ]
+        
+        return filtered_items[:num_items]
+    
+    def get_top_hot_items(self, num_items: int = 100) -> List[Tuple[int, float]]:
+        """
+        获取全局热门物品(不考虑用户个性化)
+        
+        Args:
+            num_items: 返回物品数量
+            
+        Returns:
+            List of (item_id, score) tuples
+        """
+        return self.hot_items[:num_items]
diff --git a/Merge/back_rhj/app/models/recall/multi_recall_manager.py b/Merge/back_rhj/app/models/recall/multi_recall_manager.py
new file mode 100644
index 0000000..03cb3f8
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/multi_recall_manager.py
@@ -0,0 +1,253 @@
+from typing import List, Tuple, Dict, Any
+import numpy as np
+from collections import defaultdict
+
+from .swing_recall import SwingRecall
+from .hot_recall import HotRecall
+from .ad_recall import AdRecall
+from .usercf_recall import UserCFRecall
+
+class MultiRecallManager:
+    """
+    多路召回管理器
+    整合Swing、热度召回、广告召回和UserCF等多种召回策略
+    """
+    
+    def __init__(self, db_config: dict, recall_config: dict = None):
+        """
+        初始化多路召回管理器
+        
+        Args:
+            db_config: 数据库配置
+            recall_config: 召回配置,包含各个召回器的参数和召回数量
+        """
+        self.db_config = db_config
+        
+        # 默认配置
+        default_config = {
+            'swing': {
+                'enabled': True,
+                'num_items': 15,
+                'alpha': 0.5
+            },
+            'hot': {
+                'enabled': True,
+                'num_items': 10
+            },
+            'ad': {
+                'enabled': True,
+                'num_items': 2
+            },
+            'usercf': {
+                'enabled': True,
+                'num_items': 10,
+                'min_common_items': 3,
+                'num_similar_users': 50
+            }
+        }
+        
+        # 合并用户配置
+        self.config = default_config
+        if recall_config:
+            for key, value in recall_config.items():
+                if key in self.config:
+                    self.config[key].update(value)
+                else:
+                    self.config[key] = value
+        
+        # 初始化各个召回器
+        self.recalls = {}
+        self._init_recalls()
+        
+    def _init_recalls(self):
+        """初始化各个召回器"""
+        if self.config['swing']['enabled']:
+            self.recalls['swing'] = SwingRecall(
+                self.db_config, 
+                alpha=self.config['swing']['alpha']
+            )
+            
+        if self.config['hot']['enabled']:
+            self.recalls['hot'] = HotRecall(self.db_config)
+            
+        if self.config['ad']['enabled']:
+            self.recalls['ad'] = AdRecall(self.db_config)
+            
+        if self.config['usercf']['enabled']:
+            self.recalls['usercf'] = UserCFRecall(
+                self.db_config,
+                min_common_items=self.config['usercf']['min_common_items']
+            )
+    
+    def train_all(self):
+        """训练所有召回器"""
+        print("开始训练多路召回模型...")
+        
+        for name, recall_model in self.recalls.items():
+            print(f"训练 {name} 召回器...")
+            recall_model.train()
+            
+        print("所有召回器训练完成!")
+    
+    def recall(self, user_id: int, total_items: int = 200) -> Tuple[List[int], List[float], Dict[str, List[Tuple[int, float]]]]:
+        """
+        执行多路召回
+        
+        Args:
+            user_id: 用户ID
+            total_items: 总召回物品数量
+            
+        Returns:
+            Tuple containing:
+            - List of item IDs
+            - List of scores
+            - Dict of recall results by source
+        """
+        recall_results = {}
+        all_candidates = defaultdict(list)  # item_id -> [(source, score), ...]
+        
+        # 执行各路召回
+        for source, recall_model in self.recalls.items():
+            if not self.config[source]['enabled']:
+                continue
+                
+            num_items = self.config[source]['num_items']
+            
+            # 特殊处理UserCF的参数
+            if source == 'usercf':
+                items_scores = recall_model.recall(
+                    user_id, 
+                    num_items=num_items,
+                    num_similar_users=self.config[source]['num_similar_users']
+                )
+            else:
+                items_scores = recall_model.recall(user_id, num_items=num_items)
+            
+            recall_results[source] = items_scores
+            
+            # 收集候选物品
+            for item_id, score in items_scores:
+                all_candidates[item_id].append((source, score))
+        
+        # 融合多路召回结果
+        final_candidates = self._merge_recall_results(all_candidates, total_items)
+        
+        # 分离item_ids和scores
+        item_ids = [item_id for item_id, _ in final_candidates]
+        scores = [score for _, score in final_candidates]
+        
+        return item_ids, scores, recall_results
+    
+    def _merge_recall_results(self, all_candidates: Dict[int, List[Tuple[str, float]]], 
+                            total_items: int) -> List[Tuple[int, float]]:
+        """
+        融合多路召回结果
+        
+        Args:
+            all_candidates: 所有候选物品及其来源和分数
+            total_items: 最终返回的物品数量
+            
+        Returns:
+            List of (item_id, final_score) tuples
+        """
+        # 定义各召回源的权重
+        source_weights = {
+            'swing': 0.3,
+            'hot': 0.2,
+            'ad': 0.1,
+            'usercf': 0.4
+        }
+        
+        final_scores = []
+        
+        for item_id, source_scores in all_candidates.items():
+            # 计算加权平均分数
+            weighted_score = 0.0
+            total_weight = 0.0
+            
+            for source, score in source_scores:
+                weight = source_weights.get(source, 0.1)
+                weighted_score += weight * score
+                total_weight += weight
+            
+            # 归一化
+            if total_weight > 0:
+                final_score = weighted_score / total_weight
+            else:
+                final_score = 0.0
+            
+            # 多样性奖励:如果物品来自多个召回源,给予额外分数
+            diversity_bonus = len(source_scores) * 0.1
+            final_score += diversity_bonus
+            
+            final_scores.append((item_id, final_score))
+        
+        # 按最终分数排序
+        final_scores.sort(key=lambda x: x[1], reverse=True)
+        
+        return final_scores[:total_items]
+    
+    def get_recall_stats(self, user_id: int) -> Dict[str, Any]:
+        """
+        获取召回统计信息
+        
+        Args:
+            user_id: 用户ID
+            
+        Returns:
+            召回统计字典
+        """
+        stats = {
+            'user_id': user_id,
+            'enabled_recalls': list(self.recalls.keys()),
+            'config': self.config
+        }
+        
+        # 获取各召回器的统计信息
+        if 'usercf' in self.recalls:
+            try:
+                user_profile = self.recalls['usercf'].get_user_profile(user_id)
+                stats['user_profile'] = user_profile
+                
+                neighbors = self.recalls['usercf'].get_user_neighbors(user_id, 5)
+                stats['similar_users'] = neighbors
+            except:
+                pass
+        
+        return stats
+    
+    def update_config(self, new_config: dict):
+        """
+        更新召回配置
+        
+        Args:
+            new_config: 新的配置字典
+        """
+        for key, value in new_config.items():
+            if key in self.config:
+                self.config[key].update(value)
+            else:
+                self.config[key] = value
+        
+        # 重新初始化召回器
+        self._init_recalls()
+    
+    def get_recall_breakdown(self, user_id: int) -> Dict[str, int]:
+        """
+        获取各召回源的物品数量分布
+        
+        Args:
+            user_id: 用户ID
+            
+        Returns:
+            各召回源的物品数量字典
+        """
+        breakdown = {}
+        
+        for source in self.recalls.keys():
+            if self.config[source]['enabled']:
+                breakdown[source] = self.config[source]['num_items']
+            else:
+                breakdown[source] = 0
+        
+        return breakdown
diff --git a/Merge/back_rhj/app/models/recall/swing_recall.py b/Merge/back_rhj/app/models/recall/swing_recall.py
new file mode 100644
index 0000000..bf7fdd6
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/swing_recall.py
@@ -0,0 +1,126 @@
+import numpy as np
+import pymysql
+from collections import defaultdict
+import math
+from typing import List, Tuple, Dict
+
+class SwingRecall:
+    """
+    Swing召回算法实现
+    基于物品相似度的协同过滤算法,能够有效处理热门物品的问题
+    """
+    
+    def __init__(self, db_config: dict, alpha: float = 0.5):
+        """
+        初始化Swing召回模型
+        
+        Args:
+            db_config: 数据库配置
+            alpha: 控制热门物品惩罚的参数,值越大惩罚越强
+        """
+        self.db_config = db_config
+        self.alpha = alpha
+        self.item_similarity = {}
+        self.user_items = defaultdict(set)
+        self.item_users = defaultdict(set)
+        
+    def _get_interaction_data(self):
+        """获取用户-物品交互数据"""
+        conn = pymysql.connect(**self.db_config)
+        try:
+            cursor = conn.cursor()
+            # 获取用户行为数据(点赞、收藏、评论等)
+            cursor.execute("""
+                SELECT DISTINCT user_id, post_id
+                FROM behaviors
+                WHERE type IN ('like', 'favorite', 'comment')
+            """)
+            interactions = cursor.fetchall()
+            
+            for user_id, post_id in interactions:
+                self.user_items[user_id].add(post_id)
+                self.item_users[post_id].add(user_id)
+                
+        finally:
+            cursor.close()
+            conn.close()
+    
+    def _calculate_swing_similarity(self):
+        """计算Swing相似度矩阵"""
+        print("开始计算Swing相似度...")
+        
+        # 获取所有物品对
+        items = list(self.item_users.keys())
+        
+        for i, item_i in enumerate(items):
+            if i % 100 == 0:
+                print(f"处理进度: {i}/{len(items)}")
+                
+            self.item_similarity[item_i] = {}
+            
+            for item_j in items[i+1:]:
+                # 获取同时交互过两个物品的用户
+                common_users = self.item_users[item_i] & self.item_users[item_j]
+                
+                if len(common_users) < 2:  # 需要至少2个共同用户
+                    similarity = 0.0
+                else:
+                    # 计算Swing相似度
+                    similarity = 0.0
+                    for u in common_users:
+                        for v in common_users:
+                            if u != v:
+                                # Swing算法的核心公式
+                                swing_weight = 1.0 / (self.alpha + len(self.user_items[u] & self.user_items[v]))
+                                similarity += swing_weight
+                    
+                    # 归一化
+                    similarity = similarity / (len(common_users) * (len(common_users) - 1))
+                
+                self.item_similarity[item_i][item_j] = similarity
+                # 对称性
+                if item_j not in self.item_similarity:
+                    self.item_similarity[item_j] = {}
+                self.item_similarity[item_j][item_i] = similarity
+        
+        print("Swing相似度计算完成")
+    
+    def train(self):
+        """训练Swing模型"""
+        self._get_interaction_data()
+        self._calculate_swing_similarity()
+    
+    def recall(self, user_id: int, num_items: int = 50) -> List[Tuple[int, float]]:
+        """
+        为用户召回相似物品
+        
+        Args:
+            user_id: 用户ID
+            num_items: 召回物品数量
+            
+        Returns:
+            List of (item_id, score) tuples
+        """
+        # 如果尚未训练,先进行训练
+        if not hasattr(self, 'item_similarity') or not self.item_similarity:
+            self.train()
+        
+        if user_id not in self.user_items:
+            return []
+        
+        # 获取用户历史交互的物品
+        user_interacted_items = self.user_items[user_id]
+        
+        # 计算候选物品的分数
+        candidate_scores = defaultdict(float)
+        
+        for item_i in user_interacted_items:
+            if item_i in self.item_similarity:
+                for item_j, similarity in self.item_similarity[item_i].items():
+                    # 排除用户已经交互过的物品
+                    if item_j not in user_interacted_items:
+                        candidate_scores[item_j] += similarity
+        
+        # 按分数排序并返回top-N
+        sorted_candidates = sorted(candidate_scores.items(), key=lambda x: x[1], reverse=True)
+        return sorted_candidates[:num_items]
diff --git a/Merge/back_rhj/app/models/recall/usercf_recall.py b/Merge/back_rhj/app/models/recall/usercf_recall.py
new file mode 100644
index 0000000..d75e6d8
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/usercf_recall.py
@@ -0,0 +1,235 @@
+import pymysql
+from typing import List, Tuple, Dict, Set
+from collections import defaultdict
+import math
+import numpy as np
+
+class UserCFRecall:
+    """
+    UserCF (User-based Collaborative Filtering) 召回算法实现
+    基于用户相似度的协同过滤算法
+    """
+    
+    def __init__(self, db_config: dict, min_common_items: int = 3):
+        """
+        初始化UserCF召回模型
+        
+        Args:
+            db_config: 数据库配置
+            min_common_items: 计算用户相似度时的最小共同物品数
+        """
+        self.db_config = db_config
+        self.min_common_items = min_common_items
+        self.user_items = defaultdict(set)
+        self.item_users = defaultdict(set)
+        self.user_similarity = {}
+        
+    def _get_user_item_interactions(self):
+        """获取用户-物品交互数据"""
+        conn = pymysql.connect(**self.db_config)
+        try:
+            cursor = conn.cursor()
+            
+            # 获取用户行为数据,考虑不同行为的权重
+            cursor.execute("""
+                SELECT user_id, post_id, type, COUNT(*) as count
+                FROM behaviors
+                WHERE type IN ('like', 'favorite', 'comment', 'view')
+                GROUP BY user_id, post_id, type
+            """)
+            
+            interactions = cursor.fetchall()
+            
+            # 构建用户-物品交互矩阵(考虑行为权重)
+            user_item_scores = defaultdict(lambda: defaultdict(float))
+            
+            # 定义不同行为的权重
+            behavior_weights = {
+                'like': 1.0,
+                'favorite': 2.0,
+                'comment': 3.0,
+                'view': 0.1
+            }
+            
+            for user_id, post_id, behavior_type, count in interactions:
+                weight = behavior_weights.get(behavior_type, 1.0)
+                score = weight * count
+                user_item_scores[user_id][post_id] += score
+            
+            # 转换为集合形式(用于相似度计算)
+            for user_id, items in user_item_scores.items():
+                # 只保留分数大于阈值的物品
+                threshold = 1.0  # 可调整阈值
+                for item_id, score in items.items():
+                    if score >= threshold:
+                        self.user_items[user_id].add(item_id)
+                        self.item_users[item_id].add(user_id)
+                        
+        finally:
+            cursor.close()
+            conn.close()
+    
+    def _calculate_user_similarity(self):
+        """计算用户相似度矩阵"""
+        print("开始计算用户相似度...")
+        
+        users = list(self.user_items.keys())
+        total_pairs = len(users) * (len(users) - 1) // 2
+        processed = 0
+        
+        for i, user_i in enumerate(users):
+            self.user_similarity[user_i] = {}
+            
+            for user_j in users[i+1:]:
+                processed += 1
+                if processed % 10000 == 0:
+                    print(f"处理进度: {processed}/{total_pairs}")
+                
+                # 获取两个用户共同交互的物品
+                common_items = self.user_items[user_i] & self.user_items[user_j]
+                
+                if len(common_items) < self.min_common_items:
+                    similarity = 0.0
+                else:
+                    # 计算余弦相似度
+                    numerator = len(common_items)
+                    denominator = math.sqrt(len(self.user_items[user_i]) * len(self.user_items[user_j]))
+                    similarity = numerator / denominator if denominator > 0 else 0.0
+                
+                self.user_similarity[user_i][user_j] = similarity
+                # 对称性
+                if user_j not in self.user_similarity:
+                    self.user_similarity[user_j] = {}
+                self.user_similarity[user_j][user_i] = similarity
+        
+        print("用户相似度计算完成")
+    
+    def train(self):
+        """训练UserCF模型"""
+        self._get_user_item_interactions()
+        self._calculate_user_similarity()
+    
+    def recall(self, user_id: int, num_items: int = 50, num_similar_users: int = 50) -> List[Tuple[int, float]]:
+        """
+        为用户召回相似用户喜欢的物品
+        
+        Args:
+            user_id: 目标用户ID
+            num_items: 召回物品数量
+            num_similar_users: 考虑的相似用户数量
+            
+        Returns:
+            List of (item_id, score) tuples
+        """
+        # 如果尚未训练,先进行训练
+        if not hasattr(self, 'user_similarity') or not self.user_similarity:
+            self.train()
+        
+        if user_id not in self.user_similarity or user_id not in self.user_items:
+            return []
+        
+        # 获取最相似的用户
+        similar_users = sorted(
+            self.user_similarity[user_id].items(),
+            key=lambda x: x[1],
+            reverse=True
+        )[:num_similar_users]
+        
+        # 获取目标用户已交互的物品
+        user_interacted_items = self.user_items[user_id]
+        
+        # 计算候选物品的分数
+        candidate_scores = defaultdict(float)
+        
+        for similar_user_id, similarity in similar_users:
+            if similarity <= 0:
+                continue
+                
+            # 获取相似用户交互的物品
+            similar_user_items = self.user_items[similar_user_id]
+            
+            for item_id in similar_user_items:
+                # 排除目标用户已经交互过的物品
+                if item_id not in user_interacted_items:
+                    candidate_scores[item_id] += similarity
+        
+        # 按分数排序并返回top-N
+        sorted_candidates = sorted(candidate_scores.items(), key=lambda x: x[1], reverse=True)
+        return sorted_candidates[:num_items]
+    
+    def get_user_neighbors(self, user_id: int, num_neighbors: int = 10) -> List[Tuple[int, float]]:
+        """
+        获取用户的相似邻居
+        
+        Args:
+            user_id: 用户ID
+            num_neighbors: 邻居数量
+            
+        Returns:
+            List of (neighbor_user_id, similarity) tuples
+        """
+        if user_id not in self.user_similarity:
+            return []
+        
+        neighbors = sorted(
+            self.user_similarity[user_id].items(),
+            key=lambda x: x[1],
+            reverse=True
+        )[:num_neighbors]
+        
+        return neighbors
+    
+    def get_user_profile(self, user_id: int) -> Dict:
+        """
+        获取用户画像信息
+        
+        Args:
+            user_id: 用户ID
+            
+        Returns:
+            用户画像字典
+        """
+        if user_id not in self.user_items:
+            return {}
+        
+        conn = pymysql.connect(**self.db_config)
+        try:
+            cursor = conn.cursor()
+            
+            # 获取用户交互的物品类别统计
+            user_item_list = list(self.user_items[user_id])
+            if not user_item_list:
+                return {}
+                
+            format_strings = ','.join(['%s'] * len(user_item_list))
+            cursor.execute(f"""
+                SELECT t.name, COUNT(*) as count
+                FROM post_tags pt
+                JOIN tags t ON pt.tag_id = t.id
+                WHERE pt.post_id IN ({format_strings})
+                GROUP BY t.name
+                ORDER BY count DESC
+            """, tuple(user_item_list))
+            
+            tag_preferences = cursor.fetchall()
+            
+            # 获取用户行为统计
+            cursor.execute("""
+                SELECT type, COUNT(*) as count
+                FROM behaviors
+                WHERE user_id = %s
+                GROUP BY type
+            """, (user_id,))
+            
+            behavior_stats = cursor.fetchall()
+            
+            return {
+                'user_id': user_id,
+                'total_interactions': len(self.user_items[user_id]),
+                'tag_preferences': dict(tag_preferences),
+                'behavior_stats': dict(behavior_stats)
+            }
+            
+        finally:
+            cursor.close()
+            conn.close()
diff --git a/Merge/back_rhj/app/models/recommend/LightGCN.py b/Merge/back_rhj/app/models/recommend/LightGCN.py
new file mode 100644
index 0000000..38b1732
--- /dev/null
+++ b/Merge/back_rhj/app/models/recommend/LightGCN.py
@@ -0,0 +1,121 @@
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+import numpy as np
+import scipy.sparse as sp
+import math
+import networkx as nx
+import random
+from copy import deepcopy
+from app.utils.parse_args import args
+from app.models.recommend.base_model import BaseModel
+from app.models.recommend.operators import EdgelistDrop
+from app.models.recommend.operators import scatter_add, scatter_sum
+
+
+init = nn.init.xavier_uniform_
+
+class LightGCN(BaseModel):
+    def __init__(self, dataset, pretrained_model=None, phase='pretrain'):
+        super().__init__(dataset)
+        self.adj = self._make_binorm_adj(dataset.graph)
+        self.edges = self.adj._indices().t()
+        self.edge_norm = self.adj._values()
+
+        self.phase = phase
+
+        self.emb_gate = lambda x: x
+
+        if self.phase == 'pretrain' or self.phase == 'vanilla' or self.phase == 'for_tune':
+            self.user_embedding = nn.Parameter(init(torch.empty(self.num_users, self.emb_size)))
+            self.item_embedding = nn.Parameter(init(torch.empty(self.num_items, self.emb_size)))
+
+
+        elif self.phase == 'finetune':
+            pre_user_emb, pre_item_emb = pretrained_model.generate()
+            self.user_embedding = nn.Parameter(pre_user_emb).requires_grad_(True)
+            self.item_embedding = nn.Parameter(pre_item_emb).requires_grad_(True)
+
+        elif self.phase == 'continue_tune':
+            # re-initialize for loading state dict
+            self.user_embedding = nn.Parameter(init(torch.empty(self.num_users, self.emb_size)))
+            self.item_embedding = nn.Parameter(init(torch.empty(self.num_items, self.emb_size)))
+
+        self.edge_dropout = EdgelistDrop()
+
+    def _agg(self, all_emb, edges, edge_norm):
+        src_emb = all_emb[edges[:, 0]]
+
+        # bi-norm
+        src_emb = src_emb * edge_norm.unsqueeze(1)
+
+        # conv
+        dst_emb = scatter_sum(src_emb, edges[:, 1], dim=0, dim_size=self.num_users+self.num_items)
+        return dst_emb
+    
+    def _edge_binorm(self, edges):
+        user_degs = scatter_add(torch.ones_like(edges[:, 0]), edges[:, 0], dim=0, dim_size=self.num_users)
+        user_degs = user_degs[edges[:, 0]]
+        item_degs = scatter_add(torch.ones_like(edges[:, 1]), edges[:, 1], dim=0, dim_size=self.num_items)
+        item_degs = item_degs[edges[:, 1]]
+        norm = torch.pow(user_degs, -0.5) * torch.pow(item_degs, -0.5)
+        return norm
+
+    def forward(self, edges, edge_norm, return_layers=False):
+        all_emb = torch.cat([self.user_embedding, self.item_embedding], dim=0)
+        all_emb = self.emb_gate(all_emb)
+        res_emb = [all_emb]
+        for l in range(args.num_layers):
+            all_emb = self._agg(res_emb[-1], edges, edge_norm)
+            res_emb.append(all_emb)
+        if not return_layers:
+            res_emb = sum(res_emb)
+            user_res_emb, item_res_emb = res_emb.split([self.num_users, self.num_items], dim=0)
+        else:
+            user_res_emb, item_res_emb = [], []
+            for emb in res_emb:
+                u_emb, i_emb = emb.split([self.num_users, self.num_items], dim=0)
+                user_res_emb.append(u_emb)
+                item_res_emb.append(i_emb)
+        return user_res_emb, item_res_emb
+    
+    def cal_loss(self, batch_data):
+        edges, dropout_mask = self.edge_dropout(self.edges, 1-args.edge_dropout, return_mask=True)
+        edge_norm = self.edge_norm[dropout_mask]
+
+        # forward
+        users, pos_items, neg_items = batch_data
+        user_emb, item_emb = self.forward(edges, edge_norm)
+        batch_user_emb = user_emb[users]
+        pos_item_emb = item_emb[pos_items]
+        neg_item_emb = item_emb[neg_items]
+        rec_loss = self._bpr_loss(batch_user_emb, pos_item_emb, neg_item_emb)
+        reg_loss = args.weight_decay * self._reg_loss(users, pos_items, neg_items)
+
+        loss = rec_loss + reg_loss
+        loss_dict = {
+            "rec_loss": rec_loss.item(),
+            "reg_loss": reg_loss.item(),
+        }
+        return loss, loss_dict
+    
+    @torch.no_grad()
+    def generate(self, return_layers=False):
+        return self.forward(self.edges, self.edge_norm, return_layers=return_layers)
+    
+    @torch.no_grad()
+    def generate_lgn(self, return_layers=False):
+        return self.forward(self.edges, self.edge_norm, return_layers=return_layers)
+    
+    @torch.no_grad()
+    def rating(self, user_emb, item_emb):
+        return torch.matmul(user_emb, item_emb.t())
+    
+    def _reg_loss(self, users, pos_items, neg_items):
+        u_emb = self.user_embedding[users]
+        pos_i_emb = self.item_embedding[pos_items]
+        neg_i_emb = self.item_embedding[neg_items]
+        reg_loss = (1/2)*(u_emb.norm(2).pow(2) +
+                          pos_i_emb.norm(2).pow(2) +
+                          neg_i_emb.norm(2).pow(2))/float(len(users))
+        return reg_loss
diff --git a/Merge/back_rhj/app/models/recommend/LightGCN_pretrained.pt b/Merge/back_rhj/app/models/recommend/LightGCN_pretrained.pt
new file mode 100644
index 0000000..825e0e2
--- /dev/null
+++ b/Merge/back_rhj/app/models/recommend/LightGCN_pretrained.pt
Binary files differ
diff --git a/Merge/back_rhj/app/models/recommend/__pycache__/LightGCN.cpython-312.pyc b/Merge/back_rhj/app/models/recommend/__pycache__/LightGCN.cpython-312.pyc
new file mode 100644
index 0000000..c87435f
--- /dev/null
+++ b/Merge/back_rhj/app/models/recommend/__pycache__/LightGCN.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/recommend/__pycache__/base_model.cpython-312.pyc b/Merge/back_rhj/app/models/recommend/__pycache__/base_model.cpython-312.pyc
new file mode 100644
index 0000000..b9d8c72
--- /dev/null
+++ b/Merge/back_rhj/app/models/recommend/__pycache__/base_model.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/recommend/__pycache__/lightgcn_scorer.cpython-312.pyc b/Merge/back_rhj/app/models/recommend/__pycache__/lightgcn_scorer.cpython-312.pyc
new file mode 100644
index 0000000..b0887a9
--- /dev/null
+++ b/Merge/back_rhj/app/models/recommend/__pycache__/lightgcn_scorer.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/recommend/__pycache__/operators.cpython-312.pyc b/Merge/back_rhj/app/models/recommend/__pycache__/operators.cpython-312.pyc
new file mode 100644
index 0000000..13bb375
--- /dev/null
+++ b/Merge/back_rhj/app/models/recommend/__pycache__/operators.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/recommend/base_model.py b/Merge/back_rhj/app/models/recommend/base_model.py
new file mode 100644
index 0000000..6c59aa6
--- /dev/null
+++ b/Merge/back_rhj/app/models/recommend/base_model.py
@@ -0,0 +1,111 @@
+import torch
+import torch.nn as nn
+from app.utils.parse_args import args
+from scipy.sparse import csr_matrix
+import scipy.sparse as sp
+import numpy as np
+import torch.nn.functional as F
+
+
+class BaseModel(nn.Module):
+    def __init__(self, dataloader):
+        super(BaseModel, self).__init__()
+        self.num_users = dataloader.num_users
+        self.num_items = dataloader.num_items
+        self.emb_size = args.emb_size
+
+    def forward(self):
+        pass
+
+    def cal_loss(self, batch_data):
+        pass
+
+    def _check_inf(self, loss, pos_score, neg_score, edge_weight):
+        # find inf idx
+        inf_idx = torch.isinf(loss) | torch.isnan(loss)
+        if inf_idx.any():
+            print("find inf in loss")
+            if type(edge_weight) != int:
+                print(edge_weight[inf_idx])
+            print(f"pos_score: {pos_score[inf_idx]}")
+            print(f"neg_score: {neg_score[inf_idx]}")
+            raise ValueError("find inf in loss")
+
+    def _make_binorm_adj(self, mat):
+        a = csr_matrix((self.num_users, self.num_users))
+        b = csr_matrix((self.num_items, self.num_items))
+        mat = sp.vstack(
+            [sp.hstack([a, mat]), sp.hstack([mat.transpose(), b])])
+        mat = (mat != 0) * 1.0
+        # mat = (mat + sp.eye(mat.shape[0])) * 1.0# MARK
+        degree = np.array(mat.sum(axis=-1))
+        d_inv_sqrt = np.reshape(np.power(degree, -0.5), [-1])
+        d_inv_sqrt[np.isinf(d_inv_sqrt)] = 0.0
+        d_inv_sqrt_mat = sp.diags(d_inv_sqrt)
+        mat = mat.dot(d_inv_sqrt_mat).transpose().dot(
+            d_inv_sqrt_mat).tocoo()
+
+        # make torch tensor
+        idxs = torch.from_numpy(np.vstack([mat.row, mat.col]).astype(np.int64))
+        vals = torch.from_numpy(mat.data.astype(np.float32))
+        shape = torch.Size(mat.shape)
+        return torch.sparse.FloatTensor(idxs, vals, shape).to(args.device)
+    
+    def _make_binorm_adj_self_loop(self, mat):
+        a = csr_matrix((self.num_users, self.num_users))
+        b = csr_matrix((self.num_items, self.num_items))
+        mat = sp.vstack(
+            [sp.hstack([a, mat]), sp.hstack([mat.transpose(), b])])
+        mat = (mat != 0) * 1.0
+        mat = (mat + sp.eye(mat.shape[0])) * 1.0 # self loop
+        degree = np.array(mat.sum(axis=-1))
+        d_inv_sqrt = np.reshape(np.power(degree, -0.5), [-1])
+        d_inv_sqrt[np.isinf(d_inv_sqrt)] = 0.0
+        d_inv_sqrt_mat = sp.diags(d_inv_sqrt)
+        mat = mat.dot(d_inv_sqrt_mat).transpose().dot(
+            d_inv_sqrt_mat).tocoo()
+
+        # make torch tensor
+        idxs = torch.from_numpy(np.vstack([mat.row, mat.col]).astype(np.int64))
+        vals = torch.from_numpy(mat.data.astype(np.float32))
+        shape = torch.Size(mat.shape)
+        return torch.sparse.FloatTensor(idxs, vals, shape).to(args.device)
+
+
+    def _sp_matrix_to_sp_tensor(self, sp_matrix):
+        coo = sp_matrix.tocoo()
+        indices = torch.LongTensor([coo.row, coo.col])
+        values = torch.FloatTensor(coo.data)
+        return torch.sparse.FloatTensor(indices, values, coo.shape).coalesce().to(args.device)
+
+    def _bpr_loss(self, user_emb, pos_item_emb, neg_item_emb):
+        pos_score = (user_emb * pos_item_emb).sum(dim=1)
+        neg_score = (user_emb * neg_item_emb).sum(dim=1)
+        loss = -torch.log(1e-10 + torch.sigmoid((pos_score - neg_score)))
+        self._check_inf(loss, pos_score, neg_score, 0)
+        return loss.mean()
+    
+    def _nce_loss(self, pos_score, neg_score, edge_weight=1):
+        numerator = torch.exp(pos_score)
+        denominator = torch.exp(pos_score) + torch.exp(neg_score).sum(dim=1)
+        loss = -torch.log(numerator/denominator) * edge_weight
+        self._check_inf(loss, pos_score, neg_score, edge_weight)
+        return loss.mean()
+    
+    def _infonce_loss(self, pos_1, pos_2, negs, tau):
+        pos_1 = self.cl_mlp(pos_1)
+        pos_2 = self.cl_mlp(pos_2)
+        negs = self.cl_mlp(negs)
+        pos_1 = F.normalize(pos_1, dim=-1)
+        pos_2 = F.normalize(pos_2, dim=-1)
+        negs = F.normalize(negs, dim=-1)
+        pos_score = torch.mul(pos_1, pos_2).sum(dim=1)
+        # B, 1, E * B, E, N -> B, N
+        neg_score = torch.bmm(pos_1.unsqueeze(1), negs.transpose(1, 2)).squeeze(1)
+        # infonce loss
+        numerator = torch.exp(pos_score / tau)
+        denominator = torch.exp(pos_score / tau) + torch.exp(neg_score / tau).sum(dim=1)
+        loss = -torch.log(numerator/denominator)
+        self._check_inf(loss, pos_score, neg_score, 0)
+        return loss.mean()
+    
\ No newline at end of file
diff --git a/Merge/back_rhj/app/models/recommend/operators.py b/Merge/back_rhj/app/models/recommend/operators.py
new file mode 100644
index 0000000..a508966
--- /dev/null
+++ b/Merge/back_rhj/app/models/recommend/operators.py
@@ -0,0 +1,52 @@
+import torch
+from typing import Optional, Tuple
+from torch import nn
+
+def broadcast(src: torch.Tensor, other: torch.Tensor, dim: int):
+    if dim < 0:
+        dim = other.dim() + dim
+    if src.dim() == 1:
+        for _ in range(0, dim):
+            src = src.unsqueeze(0)
+    for _ in range(src.dim(), other.dim()):
+        src = src.unsqueeze(-1)
+    src = src.expand(other.size())
+    return src
+
+def scatter_sum(src: torch.Tensor, index: torch.Tensor, dim: int = -1,
+                out: Optional[torch.Tensor] = None,
+                dim_size: Optional[int] = None) -> torch.Tensor:
+    index = broadcast(index, src, dim)
+    if out is None:
+        size = list(src.size())
+        if dim_size is not None:
+            size[dim] = dim_size
+        elif index.numel() == 0:
+            size[dim] = 0
+        else:
+            size[dim] = int(index.max()) + 1
+        out = torch.zeros(size, dtype=src.dtype, device=src.device)
+        return out.scatter_add_(dim, index, src)
+    else:
+        return out.scatter_add_(dim, index, src)
+
+def scatter_add(src: torch.Tensor, index: torch.Tensor, dim: int = -1,
+                out: Optional[torch.Tensor] = None,
+                dim_size: Optional[int] = None) -> torch.Tensor:
+    return scatter_sum(src, index, dim, out, dim_size)
+
+
+class EdgelistDrop(nn.Module):
+    def __init__(self):
+        super(EdgelistDrop, self).__init__()
+
+    def forward(self, edgeList, keep_rate, return_mask=False):
+        if keep_rate == 1.0:
+            return edgeList, torch.ones(edgeList.size(0)).type(torch.bool)
+        edgeNum = edgeList.size(0)
+        mask = (torch.rand(edgeNum) + keep_rate).floor().type(torch.bool)
+        newEdgeList = edgeList[mask, :]
+        if return_mask:
+            return newEdgeList, mask
+        else:
+            return newEdgeList
diff --git a/Merge/back_rhj/app/models/users.py b/Merge/back_rhj/app/models/users.py
new file mode 100644
index 0000000..8edc8be
--- /dev/null
+++ b/Merge/back_rhj/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")
diff --git a/Merge/back_rhj/app/routes.py b/Merge/back_rhj/app/routes.py
new file mode 100644
index 0000000..23ff49b
--- /dev/null
+++ b/Merge/back_rhj/app/routes.py
@@ -0,0 +1,325 @@
+from flask import Blueprint, request, jsonify
+from .functions.FAuth import FAuth
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+from config import Config
+from functools import wraps
+from datetime import datetime
+
+main = Blueprint('main', __name__)
+
+def token_required(f):
+    """装饰器:需要令牌验证"""
+    @wraps(f)
+    def decorated(*args, **kwargs):
+        token = request.headers.get('Authorization')
+        if not token:
+            return jsonify({'success': False, 'message': '缺少访问令牌'}), 401
+        
+        session = None
+        try:
+            # 移除Bearer前缀
+            if token.startswith('Bearer '):
+                token = token[7:]
+            
+            engine = create_engine(Config.SQLURL)
+            SessionLocal = sessionmaker(bind=engine)
+            session = SessionLocal()
+            f_auth = FAuth(session)
+            
+            user = f_auth.get_user_by_token(token)
+            if not user:
+                return jsonify({'success': False, 'message': '无效的访问令牌'}), 401
+            
+            # 将用户信息传递给路由函数
+            return f(user, *args, **kwargs)
+        except Exception as e:
+            if session:
+                session.rollback()
+            return jsonify({'success': False, 'message': '令牌验证失败'}), 401
+        finally:
+            if session:
+                session.close()
+    
+    return decorated
+
+@main.route('/login', methods=['POST'])
+def login():
+    """用户登录接口"""
+    session = None
+    try:
+        data = request.get_json()
+        
+        # 验证必填字段
+        if not data or not data.get('email') or not data.get('password'):
+            return jsonify({
+                'success': False, 
+                'message': '用户名和密码不能为空'
+            }), 400
+        
+        email = data['email']
+        password = data['password']
+        
+        # 创建数据库连接
+        engine = create_engine(Config.SQLURL)
+        SessionLocal = sessionmaker(bind=engine)
+        session = SessionLocal()
+        
+        # 执行登录
+        f_auth = FAuth(session)
+        result = f_auth.login(email, password)
+        
+        if result['success']:
+            session.commit()
+            return jsonify(result), 200
+        else:
+            return jsonify(result), 401
+            
+    except Exception as e:
+        if session:
+            session.rollback()
+        return jsonify({
+            'success': False, 
+            'message': '服务器内部错误'
+        }), 500
+    finally:
+        if session:
+            session.close()
+
+@main.route('/register', methods=['POST'])
+def register():
+    """用户注册接口"""
+    
+    engine = create_engine(Config.SQLURL)
+    SessionLocal = sessionmaker(bind=engine)
+    session = SessionLocal()
+    
+    try:
+        data = request.get_json()
+        
+        # 验证必填字段
+        if not data or not data.get('username') or not data.get('email') or not data.get('password') or not data.get('verification_code'):
+            return jsonify({
+                'success': False, 
+                'message': '用户名、邮箱和密码不能为空'
+            }), 400
+        
+        username = data['username']
+        email = data['email']
+        password = data['password']
+        verification_code = data['verification_code']
+        
+        # 简单的邮箱格式验证
+        if '@' not in email or '.' not in email:
+            return jsonify({
+                'success': False, 
+                'message': '邮箱格式不正确'
+            }), 400
+        
+        # 密码长度验证
+        if len(password) < 6:
+            return jsonify({
+                'success': False, 
+                'message': '密码长度不能少于6位'
+            }), 400
+        
+        # 执行注册
+        f_auth = FAuth(session)
+        result = f_auth.register(username, email, password, verification_code)
+        
+        if result['success']:
+            session.commit()
+            return jsonify(result), 201
+        else:
+            return jsonify(result), 400
+            
+    except Exception as e:
+        session.rollback()
+        return jsonify({
+            'success': False, 
+            'message': '服务器内部错误'
+        }), 500
+    finally:
+        session.close()
+
+@main.route('/profile', methods=['GET'])
+@token_required
+def get_profile(current_user):
+    """获取用户信息接口(需要登录)"""
+    try:
+        return jsonify({
+            'success': True,
+            'user': current_user.to_dict()
+        }), 200
+    except Exception as e:
+        return jsonify({
+            'success': False, 
+            'message': '获取用户信息失败'
+        }), 500
+
+@main.route('/logout', methods=['POST'])
+@token_required
+def logout(current_user):
+    """用户登出接口(需要登录)"""
+    try:
+        # 这里可以将令牌加入黑名单(如果需要的话)
+        return jsonify({
+            'success': True,
+            'message': '登出成功'
+        }), 200
+    except Exception as e:
+        return jsonify({
+            'success': False, 
+            'message': '登出失败'
+        }), 500
+
+@main.route('/send-verification-code', methods=['POST'])
+def send_verification_code():
+    """发送邮箱验证码接口"""
+    
+    engine = create_engine(Config.SQLURL)
+    SessionLocal = sessionmaker(bind=engine)
+    session = SessionLocal()
+    
+    try:
+        data = request.get_json()
+        
+        # 验证必填字段
+        if not data or not data.get('email'):
+            return jsonify({
+                'success': False, 
+                'message': '邮箱地址不能为空'
+            }), 400
+        
+        email = data['email']
+        verification_type = data.get('type', 'register')  # 默认为注册验证码
+        
+        # 简单的邮箱格式验证
+        if '@' not in email or '.' not in email:
+            return jsonify({
+                'success': False, 
+                'message': '邮箱格式不正确'
+            }), 400
+        
+        # 发送验证码
+        f_auth = FAuth(session)
+        result = f_auth.send_verification_email(email, verification_type)
+        
+        if result['success']:
+            session.commit()
+            return jsonify(result), 200
+        else:
+            return jsonify(result), 400
+            
+    except Exception as e:
+        session.rollback()
+        return jsonify({
+            'success': False, 
+            'message': '服务器内部错误'
+        }), 500
+    finally:
+        session.close()
+
+@main.route('/reset-password', methods=['POST'])
+def reset_password():
+    """重置密码接口"""
+    
+    engine = create_engine(Config.SQLURL)
+    SessionLocal = sessionmaker(bind=engine)
+    session = SessionLocal()
+    
+    try:
+        data = request.get_json()
+        
+        # 验证必填字段
+        if not data or not data.get('email') or not data.get('new_password') or not data.get('verification_code'):
+            return jsonify({
+                'success': False, 
+                'message': '邮箱地址、新密码和验证码不能为空'
+            }), 400
+        
+        email = data['email']
+        new_password = data['new_password']
+        verification_code = data['verification_code']
+        
+        # 简单的邮箱格式验证
+        if '@' not in email or '.' not in email:
+            return jsonify({
+                'success': False, 
+                'message': '邮箱格式不正确'
+            }), 400
+        
+        # 密码长度验证
+        if len(new_password) < 6:
+            return jsonify({
+                'success': False, 
+                'message': '密码长度不能少于6位'
+            }), 400
+        
+        # 重置密码
+        f_auth = FAuth(session)
+        result = f_auth.reset_password_with_verification(email, new_password, verification_code)
+        
+        if result['success']:
+            session.commit()
+            return jsonify(result), 200
+        else:
+            return jsonify(result), 400
+            
+    except Exception as e:
+        session.rollback()
+        return jsonify({
+            'success': False, 
+            'message': '服务器内部错误'
+        }), 500
+    finally:
+        session.close()
+
+@main.route('/test-jwt', methods=['POST'])
+@token_required
+def test_jwt(current_user):
+    """测试JWT令牌接口(需要登录)"""
+    try:
+        # 获取当前请求的token(从装饰器已验证的Authorization header)
+        auth_header = request.headers.get('Authorization')
+        current_token = auth_header[7:] if auth_header and auth_header.startswith('Bearer ') else None
+        
+        print(f"当前用户: {current_user.username}")
+        print(f"当前用户ID: {current_user.id}")
+        print(current_user.role)
+        print(f"Token验证成功: {current_token[:20]}..." if current_token else "No token")
+        
+        # 可选:检查请求体中是否有额外的token需要验证
+        data = request.get_json() or {}
+        additional_token = data.get('token')
+        
+        response_data = {
+            'success': True,
+            'message': 'JWT令牌验证成功',
+            'user': current_user.to_dict(),
+            'token_info': {
+                'header_token_verified': True,
+                'token_preview': current_token[:20] + "..." if current_token else None
+            }
+        }
+        
+        # 如果请求体中有额外的token,也验证一下
+        if additional_token:
+            try:
+                additional_result = FAuth.verify_token(additional_token)
+                response_data['additional_token_verification'] = additional_result
+                print(f"额外token验证结果: {additional_result}")
+            except Exception as e:
+                response_data['additional_token_verification'] = {
+                    'success': False,
+                    'message': f'额外token验证失败: {str(e)}'
+                }
+        
+        return jsonify(response_data), 200
+        
+    except Exception as e:
+        print(f"test_jwt 错误: {str(e)}")
+        return jsonify({
+            'success': False, 
+            'message': f'JWT令牌验证失败: {str(e)}'
+        }), 500
\ No newline at end of file
diff --git a/Merge/back_rhj/app/services/__pycache__/__init__.cpython-312.pyc b/Merge/back_rhj/app/services/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..769373b
--- /dev/null
+++ b/Merge/back_rhj/app/services/__pycache__/__init__.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/services/__pycache__/lightgcn_scorer.cpython-312.pyc b/Merge/back_rhj/app/services/__pycache__/lightgcn_scorer.cpython-312.pyc
new file mode 100644
index 0000000..2c86f52
--- /dev/null
+++ b/Merge/back_rhj/app/services/__pycache__/lightgcn_scorer.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/services/__pycache__/recommendation_service.cpython-312.pyc b/Merge/back_rhj/app/services/__pycache__/recommendation_service.cpython-312.pyc
new file mode 100644
index 0000000..da8389f
--- /dev/null
+++ b/Merge/back_rhj/app/services/__pycache__/recommendation_service.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/services/lightgcn_scorer.py b/Merge/back_rhj/app/services/lightgcn_scorer.py
new file mode 100644
index 0000000..f6aeb19
--- /dev/null
+++ b/Merge/back_rhj/app/services/lightgcn_scorer.py
@@ -0,0 +1,295 @@
+"""
+LightGCN评分服务
+用于对多路召回的结果进行LightGCN打分
+"""
+
+import torch
+import numpy as np
+from typing import List, Tuple, Dict, Any
+from app.models.recommend.LightGCN import LightGCN
+from app.utils.parse_args import args
+from app.utils.data_loader import EdgeListData
+from app.utils.graph_build import build_user_post_graph
+
+
+class LightGCNScorer:
+    """
+    LightGCN评分器
+    专门用于对多路召回结果进行精准打分
+    """
+    
+    def __init__(self):
+        """初始化LightGCN评分器"""
+        # 设备配置
+        args.device = 'cuda:7' if torch.cuda.is_available() else 'cpu'
+        args.data_path = './app/user_post_graph.txt'
+        args.pre_model_path = './app/models/recommend/LightGCN_pretrained.pt'
+        
+        # 模型相关变量
+        self.model = None
+        self.user2idx = None
+        self.post2idx = None
+        self.idx2post = None
+        self.dataset = None
+        self.user_embeddings = None
+        self.item_embeddings = None
+        
+        # 是否已初始化
+        self._initialized = False
+    
+    def _initialize_model(self):
+        """初始化LightGCN模型"""
+        if self._initialized:
+            return
+            
+        print("初始化LightGCN评分模型...")
+        
+        # 构建用户-物品映射
+        self.user2idx, self.post2idx = build_user_post_graph(return_mapping=True)
+        self.idx2post = {v: k for k, v in self.post2idx.items()}
+        
+        # 加载数据集
+        self.dataset = EdgeListData(args.data_path, args.data_path)
+        
+        # 加载预训练模型
+        pretrained_dict = torch.load(args.pre_model_path, map_location=args.device, weights_only=True)
+        pretrained_dict['user_embedding'] = pretrained_dict['user_embedding'][:self.dataset.num_users]
+        pretrained_dict['item_embedding'] = pretrained_dict['item_embedding'][:self.dataset.num_items]
+        
+        # 初始化模型
+        self.model = LightGCN(self.dataset, phase='vanilla').to(args.device)
+        self.model.load_state_dict(pretrained_dict, strict=False)
+        self.model.eval()
+        
+        # 预先计算所有用户和物品的嵌入表示
+        with torch.no_grad():
+            self.user_embeddings, self.item_embeddings = self.model.generate()
+        
+        self._initialized = True
+        print("LightGCN评分模型初始化完成")
+    
+    def score_candidates(self, user_id: int, candidate_post_ids: List[int]) -> List[float]:
+        """
+        对候选物品进行LightGCN打分
+        
+        Args:
+            user_id: 用户ID
+            candidate_post_ids: 候选物品ID列表
+            
+        Returns:
+            List[float]: 每个候选物品的LightGCN分数
+        """
+        self._initialize_model()
+        
+        # 检查用户是否存在
+        if user_id not in self.user2idx:
+            print(f"用户 {user_id} 不在训练数据中,返回零分数")
+            return [0.0] * len(candidate_post_ids)
+        
+        user_idx = self.user2idx[user_id]
+        scores = []
+        
+        print(len(candidate_post_ids), "候选物品数量")
+        
+        with torch.no_grad():
+            user_emb = self.user_embeddings[user_idx].unsqueeze(0)  # [1, emb_size]
+            
+            for post_id in candidate_post_ids:
+                if post_id not in self.post2idx:
+                    # 物品不在训练数据中,给予默认分数
+                    scores.append(0.0)
+                    continue
+                
+                post_idx = self.post2idx[post_id]
+                item_emb = self.item_embeddings[post_idx].unsqueeze(0)  # [1, emb_size]
+                
+                # 计算评分:用户嵌入和物品嵌入的内积
+                score = torch.matmul(user_emb, item_emb.t()).item()
+                scores.append(float(score))
+        
+        return scores
+    
+    def score_batch_candidates(self, user_id: int, candidate_post_ids: List[int]) -> List[float]:
+        """
+        批量对候选物品进行LightGCN打分(更高效)
+        
+        Args:
+            user_id: 用户ID
+            candidate_post_ids: 候选物品ID列表
+            
+        Returns:
+            List[float]: 每个候选物品的LightGCN分数
+        """
+        self._initialize_model()
+        
+        # 检查用户是否存在
+        if user_id not in self.user2idx:
+            print(f"用户 {user_id} 不在训练数据中,返回零分数")
+            return [0.0] * len(candidate_post_ids)
+        
+        print(len(candidate_post_ids), "候选物品数量")
+        
+        user_idx = self.user2idx[user_id]
+        
+        # 过滤出存在于训练数据中的物品
+        valid_items = []
+        valid_indices = []
+        for i, post_id in enumerate(candidate_post_ids):
+            if post_id in self.post2idx:
+                valid_items.append(self.post2idx[post_id])
+                valid_indices.append(i)
+        
+        scores = [0.0] * len(candidate_post_ids)
+        
+        if not valid_items:
+            return scores
+        
+        with torch.no_grad():
+            user_emb = self.user_embeddings[user_idx].unsqueeze(0)  # [1, emb_size]
+            
+            # 批量获取物品嵌入
+            valid_item_indices = torch.tensor(valid_items, device=args.device)
+            valid_item_embs = self.item_embeddings[valid_item_indices]  # [num_valid_items, emb_size]
+            
+            # 批量计算评分
+            batch_scores = torch.matmul(user_emb, valid_item_embs.t()).squeeze(0)  # [num_valid_items]
+            
+            # 将分数填回原位置
+            for i, score in enumerate(batch_scores.cpu().numpy()):
+                original_idx = valid_indices[i]
+                scores[original_idx] = float(score)
+        
+        return scores
+    
+    def get_user_profile(self, user_id: int) -> Dict[str, Any]:
+        """
+        获取用户在LightGCN中的表示和统计信息
+        
+        Args:
+            user_id: 用户ID
+            
+        Returns:
+            Dict: 用户画像信息
+        """
+        self._initialize_model()
+        
+        if user_id not in self.user2idx:
+            return {
+                'user_id': user_id,
+                'exists_in_model': False,
+                'message': '用户不在训练数据中'
+            }
+        
+        user_idx = self.user2idx[user_id]
+        
+        with torch.no_grad():
+            user_emb = self.user_embeddings[user_idx]
+            
+            # 计算用户嵌入的统计信息
+            emb_norm = torch.norm(user_emb).item()
+            emb_mean = torch.mean(user_emb).item()
+            emb_std = torch.std(user_emb).item()
+            
+            # 找到与用户最相似的物品(基于余弦相似度)
+            user_emb_normalized = user_emb / torch.norm(user_emb)
+            item_embs_normalized = self.item_embeddings / torch.norm(self.item_embeddings, dim=1, keepdim=True)
+            
+            similarities = torch.matmul(user_emb_normalized.unsqueeze(0), item_embs_normalized.t()).squeeze(0)
+            top_k_indices = torch.topk(similarities, k=10).indices.cpu().numpy()
+            
+            top_similar_items = []
+            for idx in top_k_indices:
+                if idx < len(self.idx2post):
+                    post_id = self.idx2post[idx]
+                    similarity = similarities[idx].item()
+                    top_similar_items.append({
+                        'post_id': post_id,
+                        'similarity': float(similarity)
+                    })
+        
+        return {
+            'user_id': user_id,
+            'user_idx': user_idx,
+            'exists_in_model': True,
+            'embedding_stats': {
+                'norm': float(emb_norm),
+                'mean': float(emb_mean),
+                'std': float(emb_std),
+                'dimension': user_emb.shape[0]
+            },
+            'top_similar_items': top_similar_items
+        }
+    
+    def compare_scoring_methods(self, user_id: int, candidate_post_ids: List[int]) -> Dict[str, List[float]]:
+        """
+        比较不同的评分方法
+        
+        Args:
+            user_id: 用户ID
+            candidate_post_ids: 候选物品ID列表
+            
+        Returns:
+            Dict: 包含不同评分方法结果的字典
+        """
+        self._initialize_model()
+        
+        if user_id not in self.user2idx:
+            zero_scores = [0.0] * len(candidate_post_ids)
+            return {
+                'lightgcn_inner_product': zero_scores,
+                'lightgcn_cosine_similarity': zero_scores,
+                'message': f'用户 {user_id} 不在训练数据中'
+            }
+        
+        user_idx = self.user2idx[user_id]
+        
+        inner_product_scores = []
+        cosine_similarity_scores = []
+        
+        with torch.no_grad():
+            user_emb = self.user_embeddings[user_idx]
+            user_emb_normalized = user_emb / torch.norm(user_emb)
+            
+            for post_id in candidate_post_ids:
+                if post_id not in self.post2idx:
+                    inner_product_scores.append(0.0)
+                    cosine_similarity_scores.append(0.0)
+                    continue
+                
+                post_idx = self.post2idx[post_id]
+                item_emb = self.item_embeddings[post_idx]
+                item_emb_normalized = item_emb / torch.norm(item_emb)
+                
+                # 内积评分
+                inner_score = torch.dot(user_emb, item_emb).item()
+                inner_product_scores.append(float(inner_score))
+                
+                # 余弦相似度评分
+                cosine_score = torch.dot(user_emb_normalized, item_emb_normalized).item()
+                cosine_similarity_scores.append(float(cosine_score))
+        
+        return {
+            'lightgcn_inner_product': inner_product_scores,
+            'lightgcn_cosine_similarity': cosine_similarity_scores
+        }
+    
+    def get_model_info(self) -> Dict[str, Any]:
+        """
+        获取LightGCN模型的基本信息
+        
+        Returns:
+            Dict: 模型信息
+        """
+        self._initialize_model()
+        
+        return {
+            'model_type': 'LightGCN',
+            'device': str(args.device),
+            'num_users': self.dataset.num_users,
+            'num_items': self.dataset.num_items,
+            'embedding_size': self.user_embeddings.shape[1],
+            'num_layers': args.num_layers,
+            'pretrained_model_path': args.pre_model_path,
+            'data_path': args.data_path,
+            'initialized': self._initialized
+        }
diff --git a/Merge/back_rhj/app/services/recommendation_service.py b/Merge/back_rhj/app/services/recommendation_service.py
new file mode 100644
index 0000000..2f4de13
--- /dev/null
+++ b/Merge/back_rhj/app/services/recommendation_service.py
@@ -0,0 +1,719 @@
+import torch
+import pymysql
+import numpy as np
+import random
+from app.models.recommend.LightGCN import LightGCN
+from app.models.recall import MultiRecallManager
+from app.services.lightgcn_scorer import LightGCNScorer
+from app.utils.parse_args import args
+from app.utils.data_loader import EdgeListData
+from app.utils.graph_build import build_user_post_graph
+from config import Config
+
+class RecommendationService:
+    def __init__(self):
+        # 数据库连接配置 - 修改为redbook数据库
+        self.db_config = {
+            'host': '10.126.59.25',
+            'port': 3306,
+            'user': 'root',
+            'password': '123456',
+            'database': 'redbook',  # 使用redbook数据库
+            'charset': 'utf8mb4'
+        }
+        
+        # 模型配置
+        args.device = 'cuda:7' if torch.cuda.is_available() else 'cpu'
+        args.data_path = './app/user_post_graph.txt'  # 修改为帖子图文件
+        args.pre_model_path = './app/models/recommend/LightGCN_pretrained.pt'
+        
+        self.topk = 2  # 默认推荐数量
+        
+        # 初始化多路召回管理器
+        self.multi_recall = None
+        self.multi_recall_enabled = True  # 控制是否启用多路召回
+        
+        # 初始化LightGCN评分器
+        self.lightgcn_scorer = None
+        self.use_lightgcn_rerank = True  # 控制是否使用LightGCN对多路召回结果重新打分
+        
+        # 多路召回配置
+        self.recall_config = {
+            'swing': {
+                'enabled': True,
+                'num_items': 20,  # 增加召回数量
+                'alpha': 0.5
+            },
+            'hot': {
+                'enabled': True,
+                'num_items': 15  # 增加热度召回数量
+            },
+            'ad': {
+                'enabled': True,
+                'num_items': 5   # 增加广告召回数量
+            },
+            'usercf': {
+                'enabled': True,
+                'num_items': 15,
+                'min_common_items': 1,  # 降低阈值,从3改为1
+                'num_similar_users': 20  # 减少相似用户数量以提高效率
+            }
+        }
+    
+    def calculate_tag_similarity(self, tags1, tags2):
+        """
+        计算两个帖子标签的相似度
+        输入: tags1, tags2 - 标签字符串,以逗号分隔
+        输出: 相似度分数(0-1之间)
+        """
+        if not tags1 or not tags2:
+            return 0.0
+        
+        # 将标签字符串转换为集合
+        set1 = set(tag.strip() for tag in tags1.split(',') if tag.strip())
+        set2 = set(tag.strip() for tag in tags2.split(',') if tag.strip())
+        
+        if not set1 or not set2:
+            return 0.0
+        
+        # 计算标签重叠比例(Jaccard相似度)
+        intersection = len(set1.intersection(set2))
+        union = len(set1.union(set2))
+        
+        return intersection / union if union > 0 else 0.0
+    
+    def mmr_rerank_with_ads(self, post_ids, scores, theta=0.5, target_size=None):
+        """
+        使用MMR算法重新排序推荐结果,并在过程中加入广告约束
+        输入:
+        - post_ids: 帖子ID列表
+        - scores: 对应的推荐分数列表
+        - theta: 平衡相关性和多样性的参数(0.5表示各占一半)
+        - target_size: 目标结果数量,默认与输入相同
+        输出: 重排后的(post_ids, scores),每5条帖子包含1条广告
+        """
+        if target_size is None:
+            target_size = len(post_ids)
+        
+        if len(post_ids) <= 1:
+            return post_ids, scores
+        
+        # 获取帖子标签信息和广告标识
+        conn = pymysql.connect(**self.db_config)
+        cursor = conn.cursor()
+        
+        try:
+            # 查询所有候选帖子的标签和广告标识
+            format_strings = ','.join(['%s'] * len(post_ids))
+            cursor.execute(
+                f"""SELECT p.id, p.is_advertisement, 
+                          COALESCE(GROUP_CONCAT(t.name), '') as tags
+                    FROM posts p
+                    LEFT JOIN post_tags pt ON p.id = pt.post_id
+                    LEFT JOIN tags t ON pt.tag_id = t.id 
+                    WHERE p.id IN ({format_strings}) AND p.status = 'published'
+                    GROUP BY p.id, p.is_advertisement""",
+                tuple(post_ids)
+            )
+            post_info_rows = cursor.fetchall()
+            post_tags = {}
+            post_is_ad = {}
+            
+            for row in post_info_rows:
+                post_id, is_ad, tags = row
+                post_tags[post_id] = tags or ""
+                post_is_ad[post_id] = bool(is_ad)
+            
+            # 对于没有查询到的帖子,设置默认值
+            for post_id in post_ids:
+                if post_id not in post_tags:
+                    post_tags[post_id] = ""
+                    post_is_ad[post_id] = False
+            
+            # 获取额外的广告帖子作为候选
+            cursor.execute("""
+                SELECT id, heat FROM posts 
+                WHERE is_advertisement = 1 AND status = 'published' 
+                AND id NOT IN ({})
+                ORDER BY heat DESC
+                LIMIT 50
+            """.format(format_strings), tuple(post_ids))
+            extra_ad_rows = cursor.fetchall()
+            
+        finally:
+            cursor.close()
+            conn.close()
+        
+        # 分离普通帖子和广告帖子
+        normal_candidates = []
+        ad_candidates = []
+        
+        for post_id, score in zip(post_ids, scores):
+            if post_is_ad[post_id]:
+                ad_candidates.append((post_id, score))
+            else:
+                normal_candidates.append((post_id, score))
+        
+        # 添加额外的广告候选
+        for ad_id, heat in extra_ad_rows:
+            # 为广告帖子设置标签和广告标识
+            post_tags[ad_id] = ""  # 广告帖子暂时设置为空标签
+            post_is_ad[ad_id] = True
+            ad_score = float(heat) / 1000.0  # 将热度转换为分数
+            ad_candidates.append((ad_id, ad_score))
+        
+        # 排序候选列表
+        normal_candidates.sort(key=lambda x: x[1], reverse=True)
+        ad_candidates.sort(key=lambda x: x[1], reverse=True)
+        
+        # MMR算法实现,加入广告约束
+        selected = []
+        normal_idx = 0
+        ad_idx = 0
+        
+        while len(selected) < target_size:
+            current_position = len(selected)
+            
+            # 检查是否需要插入广告(每5个位置插入1个广告)
+            if (current_position + 1) % 5 == 0 and ad_idx < len(ad_candidates):
+                # 插入广告
+                selected.append(ad_candidates[ad_idx])
+                ad_idx += 1
+            else:
+                # 使用MMR选择普通帖子
+                if normal_idx >= len(normal_candidates):
+                    break
+                
+                best_score = -float('inf')
+                best_local_idx = normal_idx
+                
+                # 在剩余的普通候选中选择最佳的
+                for i in range(normal_idx, min(normal_idx + 10, len(normal_candidates))):
+                    post_id, relevance_score = normal_candidates[i]
+                    
+                    # 计算与已选帖子的最大相似度
+                    max_similarity = 0.0
+                    current_tags = post_tags[post_id]
+                    
+                    for selected_post_id, _ in selected:
+                        selected_tags = post_tags[selected_post_id]
+                        similarity = self.calculate_tag_similarity(current_tags, selected_tags)
+                        max_similarity = max(max_similarity, similarity)
+                    
+                    # 计算MMR分数
+                    mmr_score = theta * relevance_score - (1 - theta) * max_similarity
+                    
+                    if mmr_score > best_score:
+                        best_score = mmr_score
+                        best_local_idx = i
+                
+                # 选择最佳候选
+                selected.append(normal_candidates[best_local_idx])
+                # 将选中的元素移到已处理区域
+                normal_candidates[normal_idx], normal_candidates[best_local_idx] = \
+                    normal_candidates[best_local_idx], normal_candidates[normal_idx]
+                normal_idx += 1
+        
+        # 提取重排后的结果
+        reranked_post_ids = [post_id for post_id, _ in selected]
+        reranked_scores = [score for _, score in selected]
+        
+        return reranked_post_ids, reranked_scores
+    
+    def insert_advertisements(self, post_ids, scores):
+        """
+        在推荐结果中插入广告,每5条帖子插入1条广告
+        输入: post_ids, scores - 原始推荐结果
+        输出: 插入广告后的(post_ids, scores)
+        """
+        # 获取可用的广告帖子
+        conn = pymysql.connect(**self.db_config)
+        cursor = conn.cursor()
+        
+        try:
+            cursor.execute("""
+                SELECT id, heat FROM posts 
+                WHERE is_advertisement = 1 AND status = 'published' 
+                ORDER BY heat DESC
+                LIMIT 50
+            """)
+            ad_rows = cursor.fetchall()
+            
+            if not ad_rows:
+                # 没有广告,直接返回原结果
+                return post_ids, scores
+            
+            # 可用的广告帖子(排除已在推荐结果中的)
+            available_ads = [(ad_id, heat) for ad_id, heat in ad_rows if ad_id not in post_ids]
+            
+            if not available_ads:
+                # 没有可用的新广告,直接返回原结果
+                return post_ids, scores
+            
+        finally:
+            cursor.close()
+            conn.close()
+        
+        # 插入广告的逻辑
+        result_posts = []
+        result_scores = []
+        ad_index = 0
+        
+        for i, (post_id, score) in enumerate(zip(post_ids, scores)):
+            result_posts.append(post_id)
+            result_scores.append(score)
+            
+            # 每5条帖子后插入一条广告
+            if (i + 1) % 5 == 0 and ad_index < len(available_ads):
+                ad_id, ad_heat = available_ads[ad_index]
+                result_posts.append(ad_id)
+                result_scores.append(float(ad_heat) / 1000.0)  # 将热度转换为分数范围
+                ad_index += 1
+        
+        return result_posts, result_scores
+
+    def user_cold_start(self, topk=None):
+        """
+        冷启动:直接返回热度最高的topk个帖子详细信息
+        """
+        if topk is None:
+            topk = self.topk
+            
+        conn = pymysql.connect(**self.db_config)
+        cursor = conn.cursor()
+
+        try:
+            # 查询热度最高的topk个帖子
+            cursor.execute("""
+                SELECT p.id, p.user_id, p.title, p.content, p.type, p.heat, p.created_at
+                FROM posts p 
+                WHERE p.status = 'published' 
+                ORDER BY p.heat DESC 
+                LIMIT %s
+            """, (topk,))
+            post_rows = cursor.fetchall()
+            post_ids = [row[0] for row in post_rows]
+            post_map = {row[0]: row for row in post_rows}
+
+            # 查询用户信息
+            owner_ids = list(set(row[1] for row in post_rows))
+            if owner_ids:
+                format_strings_user = ','.join(['%s'] * len(owner_ids))
+                cursor.execute(
+                    f"SELECT id, username FROM users WHERE id IN ({format_strings_user})",
+                    tuple(owner_ids)
+                )
+                user_rows = cursor.fetchall()
+                user_map = {row[0]: row[1] for row in user_rows}
+            else:
+                user_map = {}
+
+            # 查询帖子标签
+            if post_ids:
+                format_strings = ','.join(['%s'] * len(post_ids))
+                cursor.execute(
+                    f"""SELECT pt.post_id, GROUP_CONCAT(t.name) as tags
+                        FROM post_tags pt 
+                        JOIN tags t ON pt.tag_id = t.id 
+                        WHERE pt.post_id IN ({format_strings})
+                        GROUP BY pt.post_id""",
+                    tuple(post_ids)
+                )
+                tag_rows = cursor.fetchall()
+                tag_map = {row[0]: row[1] for row in tag_rows}
+            else:
+                tag_map = {}
+
+            post_list = []
+            for post_id in post_ids:
+                row = post_map.get(post_id)
+                if not row:
+                    continue
+                owner_user_id = row[1]
+                post_list.append({
+                    'post_id': post_id,
+                    'title': row[2],
+                    'content': row[3][:200] + '...' if len(row[3]) > 200 else row[3],  # 截取前200字符
+                    'type': row[4],
+                    'username': user_map.get(owner_user_id, ""),
+                    'heat': row[5],
+                    'tags': tag_map.get(post_id, ""),
+                    'created_at': str(row[6]) if row[6] else ""
+                })
+            return post_list
+        finally:
+            cursor.close()
+            conn.close()
+
+    def run_inference(self, user_id, topk=None, use_multi_recall=None):
+        """
+        推荐推理主函数
+        
+        Args:
+            user_id: 用户ID
+            topk: 推荐数量
+            use_multi_recall: 是否使用多路召回,None表示使用默认设置
+        """
+        if topk is None:
+            topk = self.topk
+        
+        # 决定使用哪种召回方式
+        if use_multi_recall is None:
+            use_multi_recall = self.multi_recall_enabled
+        
+        return self._run_multi_recall_inference(user_id, topk)
+    
+    def _run_multi_recall_inference(self, user_id, topk):
+        """使用多路召回进行推荐,并可选择使用LightGCN重新打分"""
+        try:
+            # 初始化多路召回(如果尚未初始化)
+            self.init_multi_recall()
+            
+            # 执行多路召回,召回更多候选物品
+            total_candidates = min(topk * 10, 500)  # 召回候选数是最终推荐数的10倍
+            candidate_post_ids, candidate_scores, recall_breakdown = self.multi_recall_inference(
+                user_id, total_candidates
+            )
+            
+            if not candidate_post_ids:
+                # 如果多路召回没有结果,回退到冷启动
+                print(f"用户 {user_id} 多路召回无结果,使用冷启动")
+                return self.user_cold_start(topk)
+            
+            print(f"用户 {user_id} 多路召回候选数量: {len(candidate_post_ids)}")
+            print(f"召回来源分布: {self._get_recall_source_stats(recall_breakdown)}")
+            
+            # 如果启用LightGCN重新打分,使用LightGCN对候选结果进行评分
+            if self.use_lightgcn_rerank:
+                print("使用LightGCN对多路召回结果进行重新打分...")
+                lightgcn_scores = self._get_lightgcn_scores(user_id, candidate_post_ids)
+                
+                # 直接使用LightGCN分数,不进行融合
+                final_scores = lightgcn_scores
+                
+                print(f"LightGCN打分完成,分数范围: [{min(lightgcn_scores):.4f}, {max(lightgcn_scores):.4f}]")
+                print(f"使用LightGCN分数进行重排")
+            else:
+                # 使用原始多路召回分数
+                final_scores = candidate_scores
+            
+            # 使用MMR算法重排,包含广告约束
+            final_post_ids, final_scores = self.mmr_rerank_with_ads(
+                candidate_post_ids, final_scores, theta=0.5, target_size=topk
+            )
+            
+            return final_post_ids, final_scores
+            
+        except Exception as e:
+            print(f"多路召回失败: {str(e)},回退到LightGCN")
+            return self._run_lightgcn_inference(user_id, topk)
+    
+    def _run_lightgcn_inference(self, user_id, topk):
+        """使用原始LightGCN进行推荐"""
+        user2idx, post2idx = build_user_post_graph(return_mapping=True)
+        idx2post = {v: k for k, v in post2idx.items()}
+
+        if user_id not in user2idx:
+            # 冷启动
+            return self.user_cold_start(topk)
+
+        user_idx = user2idx[user_id]
+
+        dataset = EdgeListData(args.data_path, args.data_path)
+        pretrained_dict = torch.load(args.pre_model_path, map_location=args.device, weights_only=True)
+        pretrained_dict['user_embedding'] = pretrained_dict['user_embedding'][:dataset.num_users]
+        pretrained_dict['item_embedding'] = pretrained_dict['item_embedding'][:dataset.num_items]
+
+        model = LightGCN(dataset, phase='vanilla').to(args.device)
+        model.load_state_dict(pretrained_dict, strict=False)
+        model.eval()
+
+        with torch.no_grad():
+            user_emb, item_emb = model.generate()
+            user_vec = user_emb[user_idx].unsqueeze(0)
+            scores = model.rating(user_vec, item_emb).squeeze(0)
+            
+            # 获取所有物品的分数(而不是只取top候选)
+            all_scores = scores.cpu().numpy()
+            all_post_ids = [idx2post[idx] for idx in range(len(all_scores))]
+            
+            # 过滤掉分数为负的物品,只保留正分数的候选
+            positive_candidates = [(post_id, score) for post_id, score in zip(all_post_ids, all_scores) if score > 0]
+            
+            if not positive_candidates:
+                # 如果没有正分数的候选,取分数最高的一些
+                sorted_candidates = sorted(zip(all_post_ids, all_scores), key=lambda x: x[1], reverse=True)
+                positive_candidates = sorted_candidates[:min(100, len(sorted_candidates))]
+            
+            candidate_post_ids = [post_id for post_id, _ in positive_candidates]
+            candidate_scores = [score for _, score in positive_candidates]
+
+        print(f"用户 {user_id} 的LightGCN候选物品数量: {len(candidate_post_ids)}")
+
+        # 使用MMR算法重排,包含广告约束,theta=0.5平衡相关性和多样性
+        final_post_ids, final_scores = self.mmr_rerank_with_ads(
+            candidate_post_ids, candidate_scores, theta=0.5, target_size=topk
+        )
+
+        return final_post_ids, final_scores
+    
+    def _get_recall_source_stats(self, recall_breakdown):
+        """获取召回来源统计"""
+        stats = {}
+        for source, items in recall_breakdown.items():
+            stats[source] = len(items)
+        return stats
+
+    def get_post_info(self, topk_post_ids, topk_scores=None):
+        """
+        输入: topk_post_ids(帖子ID列表),topk_scores(对应的打分列表,可选)
+        输出: 推荐帖子的详细信息列表,每个元素为dict
+        """
+        if not topk_post_ids:
+            return []
+        
+        print(f"获取帖子详细信息,帖子ID列表: {topk_post_ids}")
+        if topk_scores is not None:
+            print(f"对应的推荐打分: {topk_scores}")
+
+        conn = pymysql.connect(**self.db_config)
+        cursor = conn.cursor()
+
+        try:
+            # 查询帖子基本信息
+            format_strings = ','.join(['%s'] * len(topk_post_ids))
+            cursor.execute(
+                f"""SELECT p.id, p.user_id, p.title, p.content, p.type, p.heat, p.created_at, p.is_advertisement
+                    FROM posts p 
+                    WHERE p.id IN ({format_strings}) AND p.status = 'published'""",
+                tuple(topk_post_ids)
+            )
+            post_rows = cursor.fetchall()
+            post_map = {row[0]: row for row in post_rows}
+
+            # 查询用户信息
+            owner_ids = list(set(row[1] for row in post_rows))
+            if owner_ids:
+                format_strings_user = ','.join(['%s'] * len(owner_ids))
+                cursor.execute(
+                    f"SELECT id, username FROM users WHERE id IN ({format_strings_user})",
+                    tuple(owner_ids)
+                )
+                user_rows = cursor.fetchall()
+                user_map = {row[0]: row[1] for row in user_rows}
+            else:
+                user_map = {}
+
+            # 查询帖子标签
+            cursor.execute(
+                f"""SELECT pt.post_id, GROUP_CONCAT(t.name) as tags
+                    FROM post_tags pt 
+                    JOIN tags t ON pt.tag_id = t.id 
+                    WHERE pt.post_id IN ({format_strings})
+                    GROUP BY pt.post_id""",
+                tuple(topk_post_ids)
+            )
+            tag_rows = cursor.fetchall()
+            tag_map = {row[0]: row[1] for row in tag_rows}
+
+            # 查询行为统计(点赞数、评论数等)
+            cursor.execute(
+                f"""SELECT post_id, type, COUNT(*) as count
+                    FROM behaviors 
+                    WHERE post_id IN ({format_strings}) 
+                    GROUP BY post_id, type""",
+                tuple(topk_post_ids)
+            )
+            behavior_rows = cursor.fetchall()
+            behavior_stats = {}
+            for row in behavior_rows:
+                post_id, behavior_type, count = row
+                if post_id not in behavior_stats:
+                    behavior_stats[post_id] = {}
+                behavior_stats[post_id][behavior_type] = count
+
+            post_list = []
+            for i, post_id in enumerate(topk_post_ids):
+                row = post_map.get(post_id)
+                if not row:
+                    print(f"帖子ID {post_id} 不存在或未发布,跳过")
+                    continue
+                owner_user_id = row[1]
+                stats = behavior_stats.get(post_id, {})
+                post_info = {
+                    'post_id': post_id,
+                    'title': row[2],
+                    'content': row[3][:200] + '...' if len(row[3]) > 200 else row[3],
+                    'type': row[4],
+                    'username': user_map.get(owner_user_id, ""),
+                    'heat': row[5],
+                    'tags': tag_map.get(post_id, ""),
+                    'created_at': str(row[6]) if row[6] else "",
+                    'is_advertisement': bool(row[7]),  # 添加广告标识
+                    'like_count': stats.get('like', 0),
+                    'comment_count': stats.get('comment', 0),
+                    'favorite_count': stats.get('favorite', 0),
+                    'view_count': stats.get('view', 0),
+                    'share_count': stats.get('share', 0)
+                }
+                
+                # 如果有推荐打分,添加到结果中
+                if topk_scores is not None and i < len(topk_scores):
+                    post_info['recommendation_score'] = float(topk_scores[i])
+                
+                post_list.append(post_info)
+            return post_list
+        finally:
+            cursor.close()
+            conn.close()
+
+    def get_recommendations(self, user_id, topk=None):
+        """
+        获取推荐结果的主要接口
+        """
+        try:
+            result = self.run_inference(user_id, topk)
+            # 如果是冷启动直接返回详细信息,否则查详情
+            if isinstance(result, list) and result and isinstance(result[0], dict):
+                return result
+            else:
+                # result 现在是 (topk_post_ids, topk_scores) 的元组
+                if isinstance(result, tuple) and len(result) == 2:
+                    topk_post_ids, topk_scores = result
+                    return self.get_post_info(topk_post_ids, topk_scores)
+                else:
+                    # 兼容旧的返回格式
+                    return self.get_post_info(result)
+        except Exception as e:
+            raise Exception(f"推荐系统错误: {str(e)}")
+
+    def get_all_item_scores(self, user_id):
+        """
+        获取用户对所有物品的打分
+        输入: user_id
+        输出: (post_ids, scores) - 所有帖子ID和对应的打分
+        """
+        user2idx, post2idx = build_user_post_graph(return_mapping=True)
+        idx2post = {v: k for k, v in post2idx.items()}
+
+        if user_id not in user2idx:
+            # 用户不存在,返回空结果
+            return [], []
+
+        user_idx = user2idx[user_id]
+
+        dataset = EdgeListData(args.data_path, args.data_path)
+        pretrained_dict = torch.load(args.pre_model_path, map_location=args.device, weights_only=True)
+        pretrained_dict['user_embedding'] = pretrained_dict['user_embedding'][:dataset.num_users]
+        pretrained_dict['item_embedding'] = pretrained_dict['item_embedding'][:dataset.num_items]
+
+        model = LightGCN(dataset, phase='vanilla').to(args.device)
+        model.load_state_dict(pretrained_dict, strict=False)
+        model.eval()
+
+        with torch.no_grad():
+            user_emb, item_emb = model.generate()
+            user_vec = user_emb[user_idx].unsqueeze(0)
+            scores = model.rating(user_vec, item_emb).squeeze(0)
+            
+            # 获取所有物品的ID和分数
+            all_scores = scores.cpu().numpy()
+            all_post_ids = [idx2post[idx] for idx in range(len(all_scores))]
+            
+            return all_post_ids, all_scores
+
+    def init_multi_recall(self):
+        """初始化多路召回管理器"""
+        if self.multi_recall is None:
+            print("初始化多路召回管理器...")
+            self.multi_recall = MultiRecallManager(self.db_config, self.recall_config)
+            print("多路召回管理器初始化完成")
+    
+    def init_lightgcn_scorer(self):
+        """初始化LightGCN评分器"""
+        if self.lightgcn_scorer is None:
+            print("初始化LightGCN评分器...")
+            self.lightgcn_scorer = LightGCNScorer()
+            print("LightGCN评分器初始化完成")
+    
+    def _get_lightgcn_scores(self, user_id, candidate_post_ids):
+        """
+        获取候选物品的LightGCN分数
+        
+        Args:
+            user_id: 用户ID
+            candidate_post_ids: 候选物品ID列表
+            
+        Returns:
+            List[float]: LightGCN分数列表
+        """
+        self.init_lightgcn_scorer()
+        return self.lightgcn_scorer.score_batch_candidates(user_id, candidate_post_ids)
+    
+    def _fuse_scores(self, multi_recall_scores, lightgcn_scores, alpha=0.6):
+        """
+        融合多路召回分数和LightGCN分数
+        
+        Args:
+            multi_recall_scores: 多路召回分数列表
+            lightgcn_scores: LightGCN分数列表
+            alpha: LightGCN分数的权重(0-1之间)
+            
+        Returns:
+            List[float]: 融合后的分数列表
+        """
+        if len(multi_recall_scores) != len(lightgcn_scores):
+            raise ValueError("分数列表长度不匹配")
+        
+        # 对分数进行归一化
+        def normalize_scores(scores):
+            scores = np.array(scores)
+            min_score = np.min(scores)
+            max_score = np.max(scores)
+            if max_score == min_score:
+                return np.ones_like(scores) * 0.5
+            return (scores - min_score) / (max_score - min_score)
+        
+        norm_multi_scores = normalize_scores(multi_recall_scores)
+        norm_lightgcn_scores = normalize_scores(lightgcn_scores)
+        
+        # 加权融合
+        fused_scores = alpha * norm_lightgcn_scores + (1 - alpha) * norm_multi_scores
+        
+        return fused_scores.tolist()
+    
+    def train_multi_recall(self):
+        """训练多路召回模型"""
+        self.init_multi_recall()
+        self.multi_recall.train_all()
+    
+    def update_recall_config(self, new_config):
+        """更新多路召回配置"""
+        self.recall_config.update(new_config)
+        if self.multi_recall:
+            self.multi_recall.update_config(new_config)
+    
+    def multi_recall_inference(self, user_id, total_items=200):
+        """
+        使用多路召回进行推荐
+        
+        Args:
+            user_id: 用户ID
+            total_items: 总召回物品数量
+            
+        Returns:
+            Tuple of (item_ids, scores, recall_breakdown)
+        """
+        self.init_multi_recall()
+        
+        # 执行多路召回
+        item_ids, scores, recall_results = self.multi_recall.recall(user_id, total_items)
+        
+        return item_ids, scores, recall_results
+    
+    def get_multi_recall_stats(self, user_id):
+        """获取多路召回统计信息"""
+        if self.multi_recall is None:
+            return {"error": "多路召回未初始化"}
+        
+        return self.multi_recall.get_recall_stats(user_id)
diff --git a/Merge/back_rhj/app/user_post_graph.txt b/Merge/back_rhj/app/user_post_graph.txt
new file mode 100644
index 0000000..2c66fd1
--- /dev/null
+++ b/Merge/back_rhj/app/user_post_graph.txt
@@ -0,0 +1,11 @@
+0	1 0 0 2 1 2 0 1 2 1 0 42 32 62 52 0 12 22	1749827292 1749827292 1749953091 1749953091 1749953091 1749953480 1749953480 1749953480 1749954059 1749954059 1749954059 1749955282 1749955282 1749955282 1749955282 1749955282 1749955282 1749955282	1 5 5 2 1 2 5 1 2 1 5 5 2 2 1 1 5 1
+1	2 0 0 43 33 53 1 5 13 23	1749827292 1749953091 1749953480 1749955282 1749955282 1749955282 1749955282 1749955282 1749955282 1749955282	5 5 5 1 5 2 2 5 1 2
+2	7 6 6 7 44 34 54 2 14 24	1749953091 1749953091 1749953480 1749953480 1749955282 1749955282 1749955282 1749955282 1749955282 1749955282	2 1 1 2 2 1 5 5 2 5
+3	3 0 3 0 0 1 45 35 55 15 25	1749953091 1749953091 1749953480 1749953480 1749954059 1749954059 1749955282 1749955282 1749955282 1749955282 1749955282	2 2 2 2 1 2 5 2 1 5 1
+4	0 0 2 46 36 56 6 16 26	1749953091 1749953480 1749954059 1749955282 1749955282 1749955282 1749955282 1749955282 1749955282	5 5 5 1 5 2 5 1 2
+5	37 47 57 3 7 17 27	1749955282 1749955282 1749955282 1749955282 1749955282 1749955282 1749955282	1 2 5 5 1 2 5
+6	38 48 58 8 18 28	1749955282 1749955282 1749955282 1749955282 1749955282 1749955282	2 5 1 2 5 1
+7	39 49 59 4 9 19 29	1749955282 1749955282 1749955282 1749955282 1749955282 1749955282 1749955282	5 1 2 2 5 1 2
+8	40 50 60 10 20 30	1749955282 1749955282 1749955282 1749955282 1749955282 1749955282	1 2 5 1 2 5
+9	41 51 61 11 31 21	1749955282 1749955282 1749955282 1749955282 1749955282 1749955282	2 5 1 2 1 5
+10	13	1749894174	5
diff --git a/Merge/back_rhj/app/utils/__pycache__/bloom_filter.cpython-312.pyc b/Merge/back_rhj/app/utils/__pycache__/bloom_filter.cpython-312.pyc
new file mode 100644
index 0000000..5c90537
--- /dev/null
+++ b/Merge/back_rhj/app/utils/__pycache__/bloom_filter.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/utils/__pycache__/bloom_filter_manager.cpython-312.pyc b/Merge/back_rhj/app/utils/__pycache__/bloom_filter_manager.cpython-312.pyc
new file mode 100644
index 0000000..268f1fb
--- /dev/null
+++ b/Merge/back_rhj/app/utils/__pycache__/bloom_filter_manager.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/utils/__pycache__/data_loader.cpython-312.pyc b/Merge/back_rhj/app/utils/__pycache__/data_loader.cpython-312.pyc
new file mode 100644
index 0000000..10b3571
--- /dev/null
+++ b/Merge/back_rhj/app/utils/__pycache__/data_loader.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/utils/__pycache__/graph_build.cpython-312.pyc b/Merge/back_rhj/app/utils/__pycache__/graph_build.cpython-312.pyc
new file mode 100644
index 0000000..a560e74
--- /dev/null
+++ b/Merge/back_rhj/app/utils/__pycache__/graph_build.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/utils/__pycache__/parse_args.cpython-312.pyc b/Merge/back_rhj/app/utils/__pycache__/parse_args.cpython-312.pyc
new file mode 100644
index 0000000..a88ee3b
--- /dev/null
+++ b/Merge/back_rhj/app/utils/__pycache__/parse_args.cpython-312.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/utils/data_loader.py b/Merge/back_rhj/app/utils/data_loader.py
new file mode 100644
index 0000000..c882a12
--- /dev/null
+++ b/Merge/back_rhj/app/utils/data_loader.py
@@ -0,0 +1,97 @@
+from app.utils.parse_args import args
+from os import path
+from tqdm import tqdm
+import numpy as np
+import scipy.sparse as sp
+import torch
+import networkx as nx
+from copy import deepcopy
+from collections import defaultdict
+import pandas as pd
+
+
+class EdgeListData:
+    def __init__(self, train_file, test_file, phase='pretrain', pre_dataset=None, has_time=True):
+        self.phase = phase
+        self.has_time = has_time
+        self.pre_dataset = pre_dataset
+
+        self.hour_interval = args.hour_interval_pre if phase == 'pretrain' else args.hour_interval_f
+
+        self.edgelist = []
+        self.edge_time = []
+        self.num_users = 0
+        self.num_items = 0
+        self.num_edges = 0
+
+        self.train_user_dict = {}
+        self.test_user_dict = {}
+
+        self._load_data(train_file, test_file, has_time)
+
+        if phase == 'pretrain':
+            self.user_hist_dict = self.train_user_dict
+        
+        users_has_hist = set(list(self.user_hist_dict.keys()))
+        all_users = set(list(range(self.num_users)))
+        users_no_hist = all_users - users_has_hist
+        for u in users_no_hist:
+            self.user_hist_dict[u] = []
+
+    def _read_file(self, train_file, test_file, has_time=True):
+        with open(train_file, 'r') as f:
+            for line in f:
+                line = line.strip().split('\t')
+                if not has_time:
+                    user, items = line[:2]
+                    times = " ".join(["0"] * len(items.split(" ")))
+                    weights = " ".join(["1"] * len(items.split(" "))) if len(line) < 4 else line[3]
+                else:
+                    if len(line) >= 4:  # 包含权重信息
+                        user, items, times, weights = line
+                    else:
+                        user, items, times = line
+                        weights = " ".join(["1"] * len(items.split(" ")))
+                    
+                for i in items.split(" "):
+                    self.edgelist.append((int(user), int(i)))
+                for i in times.split(" "):
+                    self.edge_time.append(int(i))
+                self.train_user_dict[int(user)] = [int(i) for i in items.split(" ")]
+
+        self.test_edge_num = 0
+        with open(test_file, 'r') as f:
+            for line in f:
+                line = line.strip().split('\t')
+                user, items = line[:2]
+                self.test_user_dict[int(user)] = [int(i) for i in items.split(" ")]
+                self.test_edge_num += len(self.test_user_dict[int(user)])
+
+    def _load_data(self, train_file, test_file, has_time=True):
+        self._read_file(train_file, test_file, has_time)
+
+        self.edgelist = np.array(self.edgelist, dtype=np.int32)
+        self.edge_time = 1 + self.timestamp_to_time_step(np.array(self.edge_time, dtype=np.int32))
+        self.num_edges = len(self.edgelist)
+        if self.pre_dataset is not None:
+            self.num_users = self.pre_dataset.num_users
+            self.num_items = self.pre_dataset.num_items
+        else:
+            self.num_users = max([np.max(self.edgelist[:, 0]) + 1, np.max(list(self.test_user_dict.keys())) + 1])
+            self.num_items = max([np.max(self.edgelist[:, 1]) + 1, np.max([np.max(self.test_user_dict[u]) for u in self.test_user_dict.keys()]) + 1])
+
+        self.graph = sp.coo_matrix((np.ones(self.num_edges), (self.edgelist[:, 0], self.edgelist[:, 1])), shape=(self.num_users, self.num_items))
+
+        if self.has_time:
+            self.edge_time_dict = defaultdict(dict)
+            for i in range(len(self.edgelist)):
+                self.edge_time_dict[self.edgelist[i][0]][self.edgelist[i][1]+self.num_users] = self.edge_time[i]
+                self.edge_time_dict[self.edgelist[i][1]+self.num_users][self.edgelist[i][0]] = self.edge_time[i]
+
+    def timestamp_to_time_step(self, timestamp_arr, least_time=None):
+        interval_hour = self.hour_interval
+        if least_time is None:
+            least_time = np.min(timestamp_arr)
+        timestamp_arr = timestamp_arr - least_time
+        timestamp_arr = timestamp_arr // (interval_hour * 3600)
+        return timestamp_arr
diff --git a/Merge/back_rhj/app/utils/graph_build.py b/Merge/back_rhj/app/utils/graph_build.py
new file mode 100644
index 0000000..a453e4e
--- /dev/null
+++ b/Merge/back_rhj/app/utils/graph_build.py
@@ -0,0 +1,115 @@
+import pymysql
+import datetime
+from collections import defaultdict
+
+SqlURL = "10.126.59.25"
+SqlPort = 3306
+Database = "redbook"  # 修改为redbook数据库
+SqlUsername = "root"
+SqlPassword = "123456"
+
+
+def fetch_user_post_data():
+    """
+    从redbook数据库的behaviors表获取用户-帖子交互数据,只包含已发布的帖子
+    """
+    conn = pymysql.connect(
+        host=SqlURL,
+        port=SqlPort,
+        user=SqlUsername,
+        password=SqlPassword,
+        database=Database,
+        charset="utf8mb4"
+    )
+    cursor = conn.cursor()
+    # 获取用户行为数据,只包含已发布帖子的行为数据
+    cursor.execute("""
+        SELECT b.user_id, b.post_id, b.type, b.value, b.created_at 
+        FROM behaviors b
+        INNER JOIN posts p ON b.post_id = p.id
+        WHERE b.type IN ('like', 'favorite', 'comment', 'view', 'share')
+        AND p.status = 'published'
+        ORDER BY b.created_at
+    """)
+    behavior_rows = cursor.fetchall()
+    cursor.close()
+    conn.close()
+    return behavior_rows
+
+
+def process_records(behavior_rows):
+    """
+    处理用户行为记录,为不同类型的行为分配权重
+    """
+    records = []
+    user_set = set()
+    post_set = set()
+    
+    # 为不同行为类型分配权重
+    behavior_weights = {
+        'view': 1,
+        'like': 2,
+        'comment': 3,
+        'share': 4,
+        'favorite': 5
+    }
+    
+    for row in behavior_rows:
+        user_id, post_id, behavior_type, value, created_at = row
+        user_set.add(user_id)
+        post_set.add(post_id)
+        
+        if isinstance(created_at, datetime.datetime):
+            ts = int(created_at.timestamp())
+        else:
+            ts = 0
+            
+        # 使用行为权重
+        weight = behavior_weights.get(behavior_type, 1) * (value or 1)
+        records.append((user_id, post_id, ts, weight))
+    
+    return records, user_set, post_set
+
+
+def build_id_maps(user_set, post_set):
+    """
+    构建用户和帖子的ID映射
+    """
+    user2idx = {uid: idx for idx, uid in enumerate(sorted(user_set))}
+    post2idx = {pid: idx for idx, pid in enumerate(sorted(post_set))}
+    return user2idx, post2idx
+
+
+def group_and_write(records, user2idx, post2idx, output_path="./app/user_post_graph.txt"):
+    """
+    将记录按用户分组并写入文件,支持行为权重
+    """
+    user_items = defaultdict(list)
+    user_times = defaultdict(list)
+    user_weights = defaultdict(list)
+    
+    for user_id, post_id, ts, weight in records:
+        uid = user2idx[user_id]
+        pid = post2idx[post_id]
+        user_items[uid].append(pid)
+        user_times[uid].append(ts)
+        user_weights[uid].append(weight)
+    
+    with open(output_path, "w", encoding="utf-8") as f:
+        for uid in sorted(user_items.keys()):
+            items = " ".join(str(item) for item in user_items[uid])
+            times = " ".join(str(t) for t in user_times[uid])
+            weights = " ".join(str(w) for w in user_weights[uid])
+            f.write(f"{uid}\t{items}\t{times}\t{weights}\n")
+
+
+def build_user_post_graph(return_mapping=False):
+    """
+    构建用户-帖子交互图
+    """
+    behavior_rows = fetch_user_post_data()
+    records, user_set, post_set = process_records(behavior_rows)
+    user2idx, post2idx = build_id_maps(user_set, post_set)
+    group_and_write(records, user2idx, post2idx)
+    if return_mapping:
+        return user2idx, post2idx
\ No newline at end of file
diff --git a/Merge/back_rhj/app/utils/parse_args.py b/Merge/back_rhj/app/utils/parse_args.py
new file mode 100644
index 0000000..82b3bb4
--- /dev/null
+++ b/Merge/back_rhj/app/utils/parse_args.py
@@ -0,0 +1,77 @@
+import argparse
+import sys
+
+def parse_args():
+    parser = argparse.ArgumentParser(description='GraphPro')
+    parser.add_argument('--phase', type=str, default='pretrain')
+    parser.add_argument('--plugin', action='store_true', default=False)
+    parser.add_argument('--save_path', type=str, default="saved" ,help='where to save model and logs')
+    parser.add_argument('--data_path', type=str, default="dataset/yelp",help='where to load data')
+    parser.add_argument('--exp_name', type=str, default='1')
+    parser.add_argument('--desc', type=str, default='')
+    parser.add_argument('--ab', type=str, default='full')
+    parser.add_argument('--log', type=int, default=1)
+
+    parser.add_argument('--device', type=str, default="cuda")
+    parser.add_argument('--model', type=str, default='GraphPro')
+    parser.add_argument('--pre_model', type=str, default='GraphPro')
+    parser.add_argument('--f_model', type=str, default='GraphPro')
+    parser.add_argument('--pre_model_path', type=str, default='pretrained_model.pt')
+
+    parser.add_argument('--hour_interval_pre', type=float, default=1)
+    parser.add_argument('--hour_interval_f', type=int, default=1)
+    parser.add_argument('--emb_dropout', type=float, default=0)
+
+    parser.add_argument('--updt_inter', type=int, default=1)
+    parser.add_argument('--samp_decay', type=float, default=0.05)
+    
+    parser.add_argument('--edge_dropout', type=float, default=0.5)
+    parser.add_argument('--emb_size', type=int, default=64)
+    parser.add_argument('--batch_size', type=int, default=2048)
+    parser.add_argument('--eval_batch_size', type=int, default=512)
+    parser.add_argument('--seed', type=int, default=2023)
+    parser.add_argument('--num_epochs', type=int, default=300)
+    parser.add_argument('--neighbor_sample_num', type=int, default=5)
+    parser.add_argument('--lr', type=float, default=0.001)
+    parser.add_argument('--weight_decay', type=float, default=1e-4)
+    parser.add_argument('--metrics', type=str, default='recall;ndcg')
+    parser.add_argument('--metrics_k', type=str, default='20')
+    parser.add_argument('--early_stop_patience', type=int, default=10)
+    parser.add_argument('--neg_num', type=int, default=1)
+    parser.add_argument('--num_layers', type=int, default=3)
+    parser.add_argument('--n_layers', type=int, default=3)
+    parser.add_argument('--ssl_reg', type=float, default=1e-4)
+    parser.add_argument('--ssl_alpha', type=float, default=1)
+    parser.add_argument('--ssl_temp', type=float, default=0.2)
+    parser.add_argument('--epoch', type=int, default=200)
+    parser.add_argument('--decay', type=float, default=1e-3)
+    parser.add_argument('--model_reg', type=float, default=1e-4)
+    parser.add_argument('--topk', type=int, default=[1, 5, 10, 20], nargs='+')
+    parser.add_argument('--aug_type', type=str, default='ED')
+    parser.add_argument('--metric_topk', type=int, default=10)
+    parser.add_argument('--n_neighbors', type=int, default=32)
+    parser.add_argument('--n_samp', type=int, default=7)
+    parser.add_argument('--temp', type=float, default=1)
+    parser.add_argument('--temp_f', type=float, default=1)
+    
+    return parser
+
+# 创建默认args,支持在没有命令行参数时使用
+try:
+    # 如果是在Flask应用中运行,使用默认参数
+    if len(sys.argv) == 1 or any(x in sys.argv[0] for x in ['flask', 'app.py', 'gunicorn']):
+        parser = parse_args()
+        args = parser.parse_args([])  # 使用空参数列表
+    else:
+        parser = parse_args()
+        args = parser.parse_args()
+except SystemExit:
+    # 如果parse_args失败,使用默认参数
+    parser = parse_args()
+    args = parser.parse_args([])
+
+if hasattr(args, 'pre_model') and hasattr(args, 'f_model'):
+    if args.pre_model == args.f_model:
+        args.model = args.pre_model
+    elif args.pre_model != 'LightGCN':
+        args.model = args.pre_model
diff --git a/Merge/back_rhj/config.py b/Merge/back_rhj/config.py
new file mode 100644
index 0000000..c249660
--- /dev/null
+++ b/Merge/back_rhj/config.py
@@ -0,0 +1,23 @@
+import os
+from dotenv import load_dotenv
+
+load_dotenv()
+
+class Config:
+    SECRET_KEY = os.environ.get('SECRET_KEY') or 'a_default_secret_key'
+    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///site.db'
+    SQLALCHEMY_TRACK_MODIFICATIONS = False
+    SQLURL = os.getenv('SQLURL')
+    SQLPORT = os.getenv('SQLPORT')
+    SQLNAME = os.getenv('SQLNAME')
+    SQLUSER = os.getenv('SQLUSER')
+    SQLPWD = os.getenv('SQLPWD')
+    JWT_SECRET_KEY = os.environ.get('JWT_SECRET_KEY') or 'jwt-secret-string'
+    
+    # 邮件配置
+    MAIL_SERVER = os.environ.get('MAIL_SERVER') or 'smtp.qq.com'
+    MAIL_PORT = int(os.environ.get('MAIL_PORT') or 587)
+    MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'true').lower() in ['true', 'on', '1']
+    MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
+    MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
+    MAIL_DEFAULT_SENDER = os.environ.get('MAIL_DEFAULT_SENDER')
\ No newline at end of file
diff --git a/Merge/back_rhj/test_bloom_filter.py b/Merge/back_rhj/test_bloom_filter.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Merge/back_rhj/test_bloom_filter.py
diff --git a/Merge/back_rhj/test_redbook_recommendation.py b/Merge/back_rhj/test_redbook_recommendation.py
new file mode 100644
index 0000000..d025ace
--- /dev/null
+++ b/Merge/back_rhj/test_redbook_recommendation.py
@@ -0,0 +1,279 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+测试基于redbook数据库的推荐系统
+"""
+
+import sys
+import os
+import time
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+
+from app.services.recommendation_service import RecommendationService
+from app.utils.graph_build import build_user_post_graph
+import pymysql
+
+def test_database_connection():
+    """测试数据库连接"""
+    print("=== 测试数据库连接 ===")
+    try:
+        db_config = {
+            'host': '10.126.59.25',
+            'port': 3306,
+            'user': 'root',
+            'password': '123456',
+            'database': 'redbook',
+            'charset': 'utf8mb4'
+        }
+        conn = pymysql.connect(**db_config)
+        cursor = conn.cursor()
+        
+        # 检查用户数量
+        cursor.execute("SELECT COUNT(*) FROM users")
+        user_count = cursor.fetchone()[0]
+        print(f"用户总数: {user_count}")
+        
+        # 检查帖子数量
+        cursor.execute("SELECT COUNT(*) FROM posts WHERE status = 'published'")
+        post_count = cursor.fetchone()[0]
+        print(f"已发布帖子数: {post_count}")
+        
+        # 检查行为数据
+        cursor.execute("SELECT type, COUNT(*) FROM behaviors GROUP BY type")
+        behavior_stats = cursor.fetchall()
+        print("行为统计:")
+        for behavior_type, count in behavior_stats:
+            print(f"  {behavior_type}: {count}")
+            
+        cursor.close()
+        conn.close()
+        print("数据库连接测试成功!")
+        return True
+    except Exception as e:
+        print(f"数据库连接失败: {e}")
+        return False
+
+def test_graph_building():
+    """测试图构建"""
+    print("\n=== 测试图构建 ===")
+    try:
+        user2idx, post2idx = build_user_post_graph(return_mapping=True)
+        print(f"用户数量: {len(user2idx)}")
+        print(f"帖子数量: {len(post2idx)}")
+        
+        # 显示前几个用户和帖子的映射
+        print("前5个用户映射:")
+        for i, (user_id, idx) in enumerate(list(user2idx.items())[:5]):
+            print(f"  用户{user_id} -> 索引{idx}")
+            
+        print("前5个帖子映射:")
+        for i, (post_id, idx) in enumerate(list(post2idx.items())[:5]):
+            print(f"  帖子{post_id} -> 索引{idx}")
+            
+        print("图构建测试成功!")
+        return True
+    except Exception as e:
+        print(f"图构建失败: {e}")
+        return False
+
+def test_cold_start_recommendation():
+    """测试冷启动推荐"""
+    print("\n=== 测试冷启动推荐 ===")
+    try:
+        service = RecommendationService()
+        
+        # 使用一个不存在的用户ID进行冷启动测试
+        fake_user_id = 999999
+        
+        # 计时开始
+        start_time = time.time()
+        recommendations = service.get_recommendations(fake_user_id, topk=10)
+        end_time = time.time()
+        
+        # 计算推荐耗时
+        recommendation_time = end_time - start_time
+        print(f"冷启动推荐耗时: {recommendation_time:.4f} 秒")
+        
+        print(f"冷启动推荐结果(用户{fake_user_id}):")
+        for i, rec in enumerate(recommendations):
+            print(f"  {i+1}. 帖子ID: {rec['post_id']}, 标题: {rec['title'][:50]}...")
+            print(f"     作者: {rec['username']}, 热度: {rec['heat']}")
+            print(f"     点赞: {rec.get('like_count', 0)}, 评论: {rec.get('comment_count', 0)}")
+            
+        print("冷启动推荐测试成功!")
+        return True
+    except Exception as e:
+        print(f"冷启动推荐失败: {e}")
+        return False
+
+def test_user_recommendation():
+    """测试用户推荐"""
+    print("\n=== 测试用户推荐 ===")
+    try:
+        service = RecommendationService()
+        
+        # 获取一个真实用户ID
+        db_config = service.db_config
+        conn = pymysql.connect(**db_config)
+        cursor = conn.cursor()
+        cursor.execute("SELECT DISTINCT user_id FROM behaviors LIMIT 1")
+        result = cursor.fetchone()
+        
+        if result:
+            user_id = result[0]
+            print(f"测试用户ID: {user_id}")
+            
+            # 查看用户的历史行为
+            cursor.execute("""
+                SELECT b.type, COUNT(*) as count
+                FROM behaviors b 
+                WHERE b.user_id = %s 
+                GROUP BY b.type
+            """, (user_id,))
+            user_behaviors = cursor.fetchall()
+            print("用户历史行为:")
+            for behavior_type, count in user_behaviors:
+                print(f"  {behavior_type}: {count}")
+            
+            cursor.close()
+            conn.close()
+            
+            # 尝试获取推荐 - 添加计时
+            print("开始生成推荐...")
+            start_time = time.time()
+            recommendations = service.get_recommendations(user_id, topk=10)
+            end_time = time.time()
+            
+            # 计算推荐耗时
+            recommendation_time = end_time - start_time
+            print(f"用户推荐耗时: {recommendation_time:.4f} 秒")
+            
+            print(f"用户推荐结果(用户{user_id}):")
+            for i, rec in enumerate(recommendations):
+                print(f"  {i+1}. 帖子ID: {rec['post_id']}, 标题: {rec['title'][:50]}...")
+                print(f"     作者: {rec['username']}, 热度: {rec['heat']}")
+                print(f"     点赞: {rec.get('like_count', 0)}, 评论: {rec.get('comment_count', 0)}")
+                if 'recommendation_score' in rec:
+                    print(f"     推荐分数: {rec['recommendation_score']:.4f}")
+                else:
+                    print(f"     热度分数: {rec['heat']}")
+                
+            print("用户推荐测试成功!")
+            return True
+        else:
+            print("没有找到有行为记录的用户")
+            cursor.close()
+            conn.close()
+            return False
+            
+    except Exception as e:
+        print(f"用户推荐失败: {e}")
+        return False
+
+def test_recommendation_performance():
+    """测试推荐性能 - 多次调用统计"""
+    print("\n=== 测试推荐性能 ===")
+    try:
+        service = RecommendationService()
+        
+        # 获取几个真实用户ID进行测试
+        db_config = service.db_config
+        conn = pymysql.connect(**db_config)
+        cursor = conn.cursor()
+        cursor.execute("SELECT DISTINCT user_id FROM behaviors LIMIT 5")
+        user_ids = [row[0] for row in cursor.fetchall()]
+        cursor.close()
+        conn.close()
+        
+        if not user_ids:
+            print("没有找到有行为记录的用户")
+            return False
+        
+        print(f"测试用户数量: {len(user_ids)}")
+        
+        # 进行多次推荐测试
+        times = []
+        test_rounds = 3  # 每个用户测试3轮
+        
+        for round_num in range(test_rounds):
+            print(f"\n第 {round_num + 1} 轮测试:")
+            round_times = []
+            
+            for i, user_id in enumerate(user_ids):
+                start_time = time.time()
+                recommendations = service.get_recommendations(user_id, topk=10)
+                end_time = time.time()
+                
+                recommendation_time = end_time - start_time
+                round_times.append(recommendation_time)
+                times.append(recommendation_time)
+                
+                print(f"  用户 {user_id}: {recommendation_time:.4f}s, 推荐数量: {len(recommendations)}")
+            
+            # 计算本轮统计
+            avg_time = sum(round_times) / len(round_times)
+            min_time = min(round_times)
+            max_time = max(round_times)
+            print(f"  本轮平均耗时: {avg_time:.4f}s, 最快: {min_time:.4f}s, 最慢: {max_time:.4f}s")
+        
+        # 计算总体统计
+        print(f"\n=== 性能统计总结 ===")
+        print(f"总测试次数: {len(times)}")
+        print(f"平均推荐耗时: {sum(times) / len(times):.4f} 秒")
+        print(f"最快推荐耗时: {min(times):.4f} 秒")
+        print(f"最慢推荐耗时: {max(times):.4f} 秒")
+        print(f"推荐耗时标准差: {(sum([(t - sum(times)/len(times))**2 for t in times]) / len(times))**0.5:.4f} 秒")
+        
+        # 性能等级评估
+        avg_time = sum(times) / len(times)
+        if avg_time < 0.1:
+            performance_level = "优秀"
+        elif avg_time < 0.5:
+            performance_level = "良好"
+        elif avg_time < 1.0:
+            performance_level = "一般"
+        else:
+            performance_level = "需要优化"
+        
+        print(f"性能评级: {performance_level}")
+        
+        print("推荐性能测试成功!")
+        return True
+        
+    except Exception as e:
+        print(f"推荐性能测试失败: {e}")
+        return False
+
+def main():
+    """主测试函数"""
+    print("开始测试基于redbook数据库的推荐系统")
+    print("=" * 50)
+    
+    tests = [
+        test_database_connection,
+        test_graph_building,
+        test_cold_start_recommendation,
+        test_user_recommendation,
+        test_recommendation_performance
+    ]
+    
+    passed = 0
+    total = len(tests)
+    
+    for test in tests:
+        try:
+            if test():
+                passed += 1
+        except Exception as e:
+            print(f"测试异常: {e}")
+    
+    print("\n" + "=" * 50)
+    print(f"测试完成: {passed}/{total} 通过")
+    
+    if passed == total:
+        print("所有测试通过!")
+    else:
+        print("部分测试失败,请检查配置和代码")
+
+if __name__ == "__main__":
+    main()
diff --git "a/Merge/back_rhj/\351\202\256\344\273\266\346\234\215\345\212\241\351\205\215\347\275\256\346\214\207\345\215\227.md" "b/Merge/back_rhj/\351\202\256\344\273\266\346\234\215\345\212\241\351\205\215\347\275\256\346\214\207\345\215\227.md"
new file mode 100644
index 0000000..b784771
--- /dev/null
+++ "b/Merge/back_rhj/\351\202\256\344\273\266\346\234\215\345\212\241\351\205\215\347\275\256\346\214\207\345\215\227.md"
@@ -0,0 +1,211 @@
+# 邮件服务配置指南
+
+## 概述
+本系统支持通过SMTP协议发送验证码邮件,支持主流邮件服务提供商,包括QQ邮箱、Gmail、163邮箱等。
+
+## 配置步骤
+
+### 1. 创建 .env 文件
+在项目根目录 `/home/ronghanji/api/API-TRM/rhj/backend/` 下创建 `.env` 文件(如果不存在),参考 `.env.example` 文件。
+
+### 2. 邮件服务配置选项
+
+#### QQ邮箱配置(推荐)
+```bash
+MAIL_SERVER=smtp.qq.com
+MAIL_PORT=587
+MAIL_USE_TLS=true
+MAIL_USERNAME=your_email@qq.com
+MAIL_PASSWORD=your_app_password
+MAIL_DEFAULT_SENDER=your_email@qq.com
+```
+
+**QQ邮箱授权码获取方法:**
+1. 登录QQ邮箱 (https://mail.qq.com)
+2. 点击"设置" → "账户"
+3. 找到"POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务"
+4. 开启"POP3/SMTP服务"或"IMAP/SMTP服务"
+5. 按照提示发送短信验证
+6. 获得16位授权码,这个授权码就是MAIL_PASSWORD
+
+#### Gmail配置
+```bash
+MAIL_SERVER=smtp.gmail.com
+MAIL_PORT=587
+MAIL_USE_TLS=true
+MAIL_USERNAME=your_email@gmail.com
+MAIL_PASSWORD=your_app_password
+MAIL_DEFAULT_SENDER=your_email@gmail.com
+```
+
+**Gmail应用密码获取方法:**
+1. 登录Google账号管理 (https://myaccount.google.com)
+2. 启用两步验证
+3. 转到"安全性" → "应用密码"
+4. 选择"邮件"和设备类型
+5. 生成16位应用密码
+
+#### 163邮箱配置
+```bash
+MAIL_SERVER=smtp.163.com
+MAIL_PORT=25
+MAIL_USE_TLS=false
+MAIL_USERNAME=your_email@163.com
+MAIL_PASSWORD=your_email_password
+MAIL_DEFAULT_SENDER=your_email@163.com
+```
+
+**163邮箱客户端授权密码获取方法:**
+1. 登录163邮箱
+2. 点击"设置" → "POP3/SMTP/IMAP"
+3. 开启"POP3/SMTP服务"
+4. 设置客户端授权密码
+
+#### 企业邮箱配置
+```bash
+MAIL_SERVER=your_company_smtp_server
+MAIL_PORT=587
+MAIL_USE_TLS=true
+MAIL_USERNAME=your_email@company.com
+MAIL_PASSWORD=your_password
+MAIL_DEFAULT_SENDER=your_email@company.com
+```
+
+### 3. 完整的 .env 配置示例
+
+```bash
+# 数据库配置
+SQLURL=mysql+pymysql://username:password@localhost:3306/redbook
+SQLPORT=3306
+SQLNAME=redbook
+SQLUSER=root
+SQLPWD=123456
+
+# JWT密钥
+JWT_SECRET_KEY=your-jwt-secret-key-here
+
+# Flask密钥
+SECRET_KEY=your-flask-secret-key-here
+
+# 邮件服务配置(选择一种)
+MAIL_SERVER=smtp.qq.com
+MAIL_PORT=587
+MAIL_USE_TLS=true
+MAIL_USERNAME=your_email@qq.com
+MAIL_PASSWORD=your_qq_auth_code
+MAIL_DEFAULT_SENDER=your_email@qq.com
+```
+
+## 配置验证
+
+### 1. 测试邮件配置
+创建一个测试脚本来验证邮件配置是否正确:
+
+```python
+# test_email.py
+import sys
+import os
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+
+from config import Config
+from app.functions.FAuth import FAuth
+from app.models import db
+from app import create_app
+
+def test_email_config():
+    """测试邮件配置"""
+    app = create_app()
+    
+    with app.app_context():
+        # 检查配置
+        print("邮件服务器配置:")
+        print(f"MAIL_SERVER: {Config.MAIL_SERVER}")
+        print(f"MAIL_PORT: {Config.MAIL_PORT}")
+        print(f"MAIL_USE_TLS: {Config.MAIL_USE_TLS}")
+        print(f"MAIL_USERNAME: {Config.MAIL_USERNAME}")
+        print(f"MAIL_DEFAULT_SENDER: {Config.MAIL_DEFAULT_SENDER}")
+        
+        if not all([Config.MAIL_USERNAME, Config.MAIL_PASSWORD, Config.MAIL_DEFAULT_SENDER]):
+            print("❌ 邮件配置不完整!")
+            return False
+        
+        # 发送测试邮件
+        auth = FAuth(db.session)
+        result = auth.send_verification_email(
+            email="test@example.com",  # 替换为您的测试邮箱
+            verification_type="register"
+        )
+        
+        if result['success']:
+            print("✅ 邮件配置正确,测试邮件发送成功!")
+            return True
+        else:
+            print(f"❌ 邮件发送失败: {result['message']}")
+            return False
+
+if __name__ == "__main__":
+    test_email_config()
+```
+
+### 2. 运行测试
+```bash
+cd /home/ronghanji/api/API-TRM/rhj/backend
+python test_email.py
+```
+
+## 常见问题和解决方案
+
+### 1. 认证失败 (535 Authentication failed)
+**原因:** 用户名或密码错误
+**解决方案:**
+- 检查邮箱地址是否正确
+- 确认使用的是授权码/应用密码,不是登录密码
+- QQ/163邮箱需要开启SMTP服务并获取授权码
+
+### 2. 连接超时
+**原因:** 网络问题或服务器地址错误
+**解决方案:**
+- 检查MAIL_SERVER和MAIL_PORT是否正确
+- 确认网络连接正常
+- 尝试使用不同的端口(如465用于SSL)
+
+### 3. TLS错误
+**原因:** TLS配置问题
+**解决方案:**
+- 检查MAIL_USE_TLS设置是否正确
+- 某些邮件服务器需要使用SSL(端口465)而不是TLS
+
+### 4. 发送频率限制
+**原因:** 邮件服务商限制发送频率
+**解决方案:**
+- 减少发送频率
+- 使用企业邮箱服务
+- 考虑使用专业的邮件服务(如SendGrid、阿里云邮件推送等)
+
+## 生产环境建议
+
+### 1. 安全性
+- 使用环境变量存储敏感信息
+- 定期更换邮箱密码和授权码
+- 使用专用的邮箱账号发送系统邮件
+
+### 2. 可靠性
+- 配置备用邮件服务器
+- 实现邮件发送重试机制
+- 监控邮件发送状态
+
+### 3. 性能优化
+- 使用异步邮件发送
+- 实现邮件队列
+- 限制发送频率
+
+## 专业邮件服务替代方案
+
+如果需要更高的可靠性和发送量,建议使用专业邮件服务:
+
+1. **阿里云邮件推送**
+2. **腾讯云邮件推送**
+3. **SendGrid**
+4. **Mailgun**
+
+这些服务通常提供更好的送达率和更高的发送限制。
diff --git a/Merge/back_trm/app.py b/Merge/back_trm/app.py
index 3c7fb86..af7fefc 100644
--- a/Merge/back_trm/app.py
+++ b/Merge/back_trm/app.py
@@ -1,8 +1,47 @@
 from app import create_app
 from flask_cors import CORS
+import os
+import psutil
+from flask import Flask,g,request
+import time
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+from config import Config
+from app.functions.Fpost import Fpost;
 
 app = create_app()
 CORS(app, resources={r"/*": {"origins": "*"}})
 
+proc=psutil.Process(os.getpid())
+@app.before_request
+def before_request():
+    g.start_time=time.time()
+    g.start_cpu=proc.cpu_times()
+    g.start_mem=proc.memory_info()
+
+@app.after_request
+def after_request(response):
+    end_time = time.time()
+    end_cpu = proc.cpu_times()
+    end_mem = proc.memory_info()
+
+    elapsed = end_time - g.start_time
+    cpu_user = end_cpu.user - g.start_cpu.user
+    cpu_sys  = end_cpu.system - g.start_cpu.system
+    mem_rss  = end_mem.rss - g.start_mem.rss
+
+    #写入性能消耗
+    engine=create_engine(Config.SQLURL)
+    SessionLocal = sessionmaker(bind=engine)
+    session = SessionLocal()
+    f=Fpost(session)
+    f.recordsyscost(
+        request.path,
+        elapsed,
+        cpu_user,
+        cpu_sys,
+        mem_rss
+    )
+    return response
 if __name__ == "__main__":
     app.run(debug=True,port=5713,host='0.0.0.0')
\ No newline at end of file
diff --git a/Merge/back_trm/app/__pycache__/__init__.cpython-310.pyc b/Merge/back_trm/app/__pycache__/__init__.cpython-310.pyc
deleted file mode 100644
index f713fad..0000000
--- a/Merge/back_trm/app/__pycache__/__init__.cpython-310.pyc
+++ /dev/null
Binary files differ
diff --git a/Merge/back_trm/app/__pycache__/__init__.cpython-312.pyc b/Merge/back_trm/app/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index ec28c7e..0000000
--- a/Merge/back_trm/app/__pycache__/__init__.cpython-312.pyc
+++ /dev/null
Binary files differ
diff --git a/Merge/back_trm/app/__pycache__/routes.cpython-310.pyc b/Merge/back_trm/app/__pycache__/routes.cpython-310.pyc
deleted file mode 100644
index 5166bf4..0000000
--- a/Merge/back_trm/app/__pycache__/routes.cpython-310.pyc
+++ /dev/null
Binary files differ
diff --git a/Merge/back_trm/app/functions/Fpost.py b/Merge/back_trm/app/functions/Fpost.py
index 7d6ccd2..2237815 100644
--- a/Merge/back_trm/app/functions/Fpost.py
+++ b/Merge/back_trm/app/functions/Fpost.py
@@ -4,6 +4,8 @@
 import hashlib
 from datetime import datetime, timedelta
 from sqlalchemy.orm import Session
+from ..models.logs import Log
+from ..models.syscost import PerformanceData
 class Fpost:
     def __init__(self,session:Session):
         self.session=session
@@ -99,4 +101,56 @@
             
         except Exception as e:
             self.session.rollback()
-            raise Exception(f"创建token失败: {str(e)}")
\ No newline at end of file
+            raise Exception(f"创建token失败: {str(e)}")
+        
+    def recordlog(self,user_id,log_type,content,ip):
+        """
+        记录日志
+        :param user_id: 用户ID
+        :param log_type: 日志类型,'access','error','behavior','system'
+        :param content: 日志内容
+        :param ip: IP地址
+        """
+        try:
+            new_log = Log(
+                user_id=user_id,
+                type=log_type,
+                content=content,
+                ip=ip
+            )
+            self.session.add(new_log)
+            self.session.commit()
+        except Exception as e:
+            self.session.rollback()
+            raise Exception(f"记录日志失败: {str(e)}")
+    
+    def getrecordlog(self):
+       res= self.session.query(Log).all()
+       return res 
+    
+    def recordsyscost(self, endpoint: str, elapsed_time: float, cpu_user: float, cpu_system: float, memory_rss: int):
+        """
+        记录系统性能消耗到 performance_data 表
+        :param endpoint: 请求接口路径
+        :param elapsed_time: 总耗时(秒)
+        :param cpu_user: 用户态 CPU 时间差(秒)
+        :param cpu_system: 系统态 CPU 时间差(秒)
+        :param memory_rss: RSS 内存增量(字节)
+        """
+        try:
+            new_record = PerformanceData(
+                endpoint=endpoint,
+                elapsed_time=elapsed_time,
+                cpu_user=cpu_user,
+                cpu_system=cpu_system,
+                memory_rss=memory_rss
+            )
+            self.session.add(new_record)
+            self.session.commit()
+        except Exception as e:
+            self.session.rollback()
+            raise Exception(f"记录系统性能消耗失败: {e}")
+    
+    def getsyscost(self):
+        res= self.session.query(PerformanceData).all()
+        return res
\ No newline at end of file
diff --git a/Merge/back_trm/app/functions/__init__.py b/Merge/back_trm/app/functions/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Merge/back_trm/app/functions/__init__.py
diff --git a/Merge/back_trm/app/functions/__pycache__/Fpost.cpython-310.pyc b/Merge/back_trm/app/functions/__pycache__/Fpost.cpython-310.pyc
index f9b1bc6..3a49c6c 100644
--- a/Merge/back_trm/app/functions/__pycache__/Fpost.cpython-310.pyc
+++ b/Merge/back_trm/app/functions/__pycache__/Fpost.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_trm/app/functions/__pycache__/__init__.cpython-310.pyc b/Merge/back_trm/app/functions/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000..ed5a973
--- /dev/null
+++ b/Merge/back_trm/app/functions/__pycache__/__init__.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_trm/app/models/__pycache__/logs.cpython-310.pyc b/Merge/back_trm/app/models/__pycache__/logs.cpython-310.pyc
new file mode 100644
index 0000000..f1e86e3
--- /dev/null
+++ b/Merge/back_trm/app/models/__pycache__/logs.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_trm/app/models/__pycache__/syscost.cpython-310.pyc b/Merge/back_trm/app/models/__pycache__/syscost.cpython-310.pyc
new file mode 100644
index 0000000..9831ffd
--- /dev/null
+++ b/Merge/back_trm/app/models/__pycache__/syscost.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_trm/app/models/logs.py b/Merge/back_trm/app/models/logs.py
new file mode 100644
index 0000000..ac8cf1c
--- /dev/null
+++ b/Merge/back_trm/app/models/logs.py
@@ -0,0 +1,19 @@
+from sqlalchemy import Column, BigInteger, Integer, Enum, Text, String, TIMESTAMP, ForeignKey, Index
+from sqlalchemy.sql import func
+from . import Base  # adjust if Base lives elsewhere
+
+class Log(Base):
+    __tablename__ = 'logs'
+    __table_args__ = (
+        Index('user_id', 'user_id'),
+        Index('idx_logs_created', 'created_at'),
+    )
+
+    id = Column(BigInteger, primary_key=True, autoincrement=True, comment='日志ID')
+    user_id = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), comment='用户ID')
+    type = Column(Enum('access', 'error', 'behavior', 'system',
+                       name='logs_type_enum'), nullable=False, comment='日志类型')
+    content = Column(Text, nullable=False, comment='日志内容')
+    ip = Column(String(45), nullable=True, comment='IP地址')
+    created_at = Column(TIMESTAMP, server_default=func.current_timestamp(),
+                        nullable=True, comment='记录时间')
diff --git a/Merge/back_trm/app/models/syscost.py b/Merge/back_trm/app/models/syscost.py
new file mode 100644
index 0000000..bbde029
--- /dev/null
+++ b/Merge/back_trm/app/models/syscost.py
@@ -0,0 +1,15 @@
+from sqlalchemy import Column, BigInteger, DateTime, String, Float, func
+from sqlalchemy.ext.declarative import declarative_base
+
+Base = declarative_base()
+
+class PerformanceData(Base):
+    __tablename__ = 'performance_data'
+
+    id = Column(BigInteger, primary_key=True, autoincrement=True)
+    record_time = Column(DateTime, nullable=False, server_default=func.now(), comment='记录时间')
+    endpoint = Column(String(255), nullable=True, comment='请求接口路径')
+    elapsed_time = Column(Float, nullable=False, comment='总耗时(秒)')
+    cpu_user = Column(Float, nullable=False, comment='用户态 CPU 时间差(秒)')
+    cpu_system = Column(Float, nullable=False, comment='系统态 CPU 时间差(秒)')
+    memory_rss = Column(BigInteger, nullable=False, comment='RSS 内存增量(字节)')
\ No newline at end of file
diff --git a/Merge/back_trm/app/routes.py b/Merge/back_trm/app/routes.py
index 41b022b..625ea6d 100644
--- a/Merge/back_trm/app/routes.py
+++ b/Merge/back_trm/app/routes.py
@@ -5,8 +5,10 @@
 from config import Config
 from flask import jsonify,request
 
+
 main = Blueprint('main', __name__)
 
+
 @main.route('/sgiveadmin',methods=['POST','GET'])
 def giveadmin():
     data=request.get_json()
@@ -17,12 +19,23 @@
     f=Fpost(session)
     checres=f.checkid(data['userid'],'superadmin')
     if(not checres):
+        f.recordlog(data['userid'],
+                     'error', 
+                     '系统需要超级管理员才能执行修改用户角色的操作,但是当前用户不是超级管理员',
+                     request.remote_addr)
         return jsonify({'status': 'error', 'message': 'Unauthorized'})
     
     res=f.giveadmin(data['targetid'])
     if not res:
+        f.recordlog(data['userid'],
+                     'error', 
+                     f"尝试修改用户{data['targetid']}角色为admin失败,用户不存在",
+                     request.remote_addr)
         return jsonify({'status': 'error', 'message': 'User not found'})
-    
+    f.recordlog(data['userid'],
+                    'behavior', 
+                    f'用户角色为admin修改成功,用户ID: {data["targetid"]} 被修改为管理员',
+                    request.remote_addr)
     return jsonify({'status': 'success', 'message': 'User role updated to admin'})
 
 @main.route('/sgiveuser',methods=['POST','GET'])
@@ -35,12 +48,23 @@
     f=Fpost(session)
     checres=f.checkid(data['userid'],'superadmin')
     if(not checres):
+        f.recordlog(data['userid'],
+                     'error', 
+                     '系统需要超级管理员才能执行修改用户角色的操作,但是当前用户不是超级管理员',
+                     request.remote_addr)
         return jsonify({'status': 'error', 'message': 'Unauthorized'})
     
     res=f.giveuser(data['targetid'])
     if not res:
+        f.recordlog(data['userid'],
+                     'error', 
+                     f"尝试修改用户{data['targetid']}为user失败,用户不存在",
+                     request.remote_addr)
         return jsonify({'status': 'error', 'message': 'User not found'})
-    
+    f.recordlog(data['userid'],
+                    'behavior', 
+                    f'用户角色修改成功,用户ID: {data["targetid"]} 被修改为普通用户',
+                    request.remote_addr)
     return jsonify({'status': 'success', 'message': 'User role updated to user'})
 
 
@@ -54,12 +78,23 @@
     f=Fpost(session)
     checres=f.checkid(data['userid'],'superadmin')
     if(not checres):
+        f.recordlog(data['userid'],
+                     'error', 
+                     '系统需要超级管理员才能执行修改用户角色的操作,但是当前用户不是超级管理员',
+                     request.remote_addr)
         return jsonify({'status': 'error', 'message': 'Unauthorized'})
     
     res=f.givesuperadmin(data['targetid'])
     if not res:
+        f.recordlog(data['userid'],
+                     'error', 
+                     f'尝试修改用户{data["targetid"]}角色为superadmin失败,用户不存在',
+                     request.remote_addr)
         return jsonify({'status': 'error', 'message': 'User not found'})
-    
+    f.recordlog(data['userid'],
+                    'behavior', 
+                    f'用户角色修改成功,用户ID: {data["targetid"]} 被修改为超级管理员',
+                    request.remote_addr)
     return jsonify({'status': 'success', 'message': 'User role updated to superadmin'})
 
 @main.route('/sgetuserlist',methods=['POST','GET'])
@@ -72,6 +107,10 @@
     f=Fpost(session)
     checres=f.checkid(data['userid'],'superadmin')
     if(not checres):
+        f.recordlog(data['userid'],
+                     'error', 
+                     '系统需要超级管理员才能执行获取用户列表的操作,但是当前用户不是超级管理员',
+                     request.remote_addr)
         return jsonify({'status': 'error', 'message': 'Unauthorized'})
     res=f.getuserlist()
     respons=[]
@@ -81,6 +120,11 @@
             'username': datai[1],
             'role': datai[2]
         })
+
+    f.recordlog(data['userid'],
+                    'access', 
+                    '获取用户列表成功',
+                    request.remote_addr)
     return jsonify(respons)
 
 @main.route('/apostlist',methods=['POST','GET'])
@@ -93,6 +137,10 @@
     f=Fpost(session)
     checres=f.checkid(data['userid'],'admin')
     if(not checres):
+        f.recordlog(data['userid'],
+                     'error', 
+                     '系统需要管理员才能执行获取帖子列表的操作,但是当前用户不是管理员',
+                     request.remote_addr)
         return jsonify({'status': 'error', 'message': 'Unauthorized'})
     res=f.getlist()
     respons=[]
@@ -102,6 +150,10 @@
             'title': datai[1],
             'status': datai[2]
         })
+    f.recordlog(data['userid'],
+                 'access', 
+                 '获取帖子列表成功',
+                 request.remote_addr)
     return jsonify(respons)
 
 @main.route('/agetpost',methods=['POST','GET'])
@@ -113,9 +165,22 @@
     f=Fpost(session)
     checres=f.checkid(data['userid'],'admin')
     if(not checres):
+        f.recordlog(data['userid'],
+                     'error', 
+                     '系统需要管理员才能执行获取帖子详情的操作,但是当前用户不是管理员',
+                     request.remote_addr)
         return jsonify({'status': 'error', 'message': 'Unauthorized'})
     res=f.getpost(data['postid'])
-
+    if not res:
+        f.recordlog(data['userid'],
+                     'error', 
+                     f'尝试获取帖子{data["postid"]}失败,帖子不存在',
+                     request.remote_addr)
+        return jsonify({'status': 'error', 'message': 'Post not found'})
+    f.recordlog(data['userid'],
+                 'access', 
+                 f'获取帖子详情成功,帖子ID: {data["postid"]}',
+                 request.remote_addr)
     return jsonify(res.to_dict() if res else {})
 
 @main.route('/areview',methods=['POST','GET'])
@@ -127,12 +192,23 @@
     f=Fpost(session)
     checres=f.checkid(data['userid'],'admin')
     if(not checres):
+        f.recordlog(data['userid'],
+                     'error', 
+                     '系统需要管理员才能执行帖子审核的操作,但是当前用户不是管理员',
+                     request.remote_addr)
         return jsonify({'status': 'error', 'message': 'Unauthorized'})
     
     res=f.review(data['postid'],data['status'])
     if not res:
+        f.recordlog(data['userid'],
+                     'error', 
+                     f'尝试审核帖子{data["postid"]}失败,帖子不存在',
+                     request.remote_addr)
         return jsonify({'status': 'error', 'message': 'Post not found'})
-    
+    f.recordlog(data['userid'],
+                 'behavior', 
+                 f'帖子审核成功,帖子ID: {data["postid"]} 状态更新为 {data["status"]}',
+                 request.remote_addr)
     return jsonify({'status': 'success', 'message': 'Post reviewed successfully'})
 
 
@@ -146,10 +222,102 @@
     f=Fpost(session)
     checres=f.checkid(data['userid'],'admin')
     if(not checres):
+        f.recordlog(data['userid'],
+                     'error', 
+                     '系统需要管理员才能执行Nginx认证的操作,但是当前用户不是管理员',
+                     request.remote_addr)
         return jsonify({'status': 'error', 'message': 'Unauthorized'})
     
     res=f.nginxauth(data['postid'],data['status'])
     if not res:
+        f.recordlog(data['userid'],
+                     'error', 
+                     f'尝试更新Nginx认证状态失败,帖子{data["postid"]}不存在',
+                     request.remote_addr)
         return jsonify({'status': 'error', 'message': 'Post not found'})
+    f.recordlog(data['userid'],
+                 'behavior', 
+                 f'Nginx认证状态更新成功,帖子ID: {data["postid"]} 状态更新为 {data["status"]}',
+                 request.remote_addr)
+    return jsonify({'status': 'success', 'message': 'Nginx auth updated successfully'})
+
+@main.route('/getsyscost',methods=['POST','GET'])
+def getsyscost():
+    data=request.get_json()
+    engine=create_engine(Config.SQLURL)
+    SessionLocal = sessionmaker(bind=engine)
+    session = SessionLocal()
+    f=Fpost(session)
+    checres=f.checkid(data['userid'],'superadmin')
+    if(not checres):
+        f.recordlog(data['userid'],
+                     'error', 
+                     '系统需要管理员才能执行获取系统性能消耗的操作,但是当前用户不是管理员',
+                     request.remote_addr)
+        return jsonify({'status': 'error', 'message': 'Unauthorized'})
     
-    return jsonify({'status': 'success', 'message': 'Nginx auth updated successfully'})
\ No newline at end of file
+    res=f.getsyscost()
+    if not res:
+        f.recordlog(data['userid'],
+                     'error', 
+                     '尝试获取系统性能消耗数据失败,数据不存在',
+                     request.remote_addr)
+        return jsonify({'status': 'error', 'message': 'No performance data found'})
+    
+    f.recordlog(data['userid'],
+                 'access', 
+                 '获取系统性能消耗数据成功',
+                 request.remote_addr)
+    resdata = []
+    for datai in res:
+        resdata.append({
+            'id': datai.id,
+            'record_time': datai.record_time.isoformat(),
+            'endpoint': datai.endpoint,
+            'elapsed_time': datai.elapsed_time,
+            'cpu_user': datai.cpu_user,
+            'cpu_system': datai.cpu_system,
+            'memory_rss': datai.memory_rss
+        })
+    return jsonify(resdata)
+@main.route('/getrecordlog',methods=['POST','GET'])
+def getrecordlog():
+    data=request.get_json()
+    engine=create_engine(Config.SQLURL)
+    SessionLocal = sessionmaker(bind=engine)
+    session = SessionLocal()
+    f=Fpost(session)
+    checres=f.checkid(data['userid'],'admin')
+    if(not checres):
+        f.recordlog(data['userid'],
+                     'error', 
+                     '系统需要管理员才能执行获取日志的操作,但是当前用户不是管理员',
+                     request.remote_addr)
+        return jsonify({'status': 'error', 'message': 'Unauthorized'})
+    
+    res=f.getrecordlog()
+    if not res:
+        f.recordlog(data['userid'],
+                     'error', 
+                     '尝试获取日志失败,日志不存在',
+                     request.remote_addr)
+        return jsonify({'status': 'error', 'message': 'No logs found'})
+    
+    f.recordlog(data['userid'],
+                 'access', 
+                 '获取日志成功',
+                 request.remote_addr)
+    
+    resdata = []
+    for datai in res:
+        resdata.append({
+            'id': datai.id,
+            'user_id': datai.user_id,
+            'type': datai.type,
+            'content': datai.content,
+            'ip': datai.ip,
+            'created_at': datai.created_at.isoformat()
+        })
+    
+    return jsonify(resdata)
+    
diff --git a/Merge/back_wzy/models/logs.py b/Merge/back_wzy/models/logs.py
new file mode 100644
index 0000000..3170dbd
--- /dev/null
+++ b/Merge/back_wzy/models/logs.py
@@ -0,0 +1,19 @@
+from sqlalchemy import Column, BigInteger, Integer, Enum, Text, String, TIMESTAMP, ForeignKey, Index
+from sqlalchemy.sql import func
+from extensions import db  # adjust if Base lives elsewhere
+
+class Log(db.Model):
+    __tablename__ = 'logs'
+    __table_args__ = (
+        Index('user_id', 'user_id'),
+        Index('idx_logs_created', 'created_at'),
+    )
+
+    id = Column(BigInteger, primary_key=True, autoincrement=True, comment='日志ID')
+    user_id = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), comment='用户ID')
+    type = Column(Enum('access', 'error', 'behavior', 'system',
+                       name='logs_type_enum'), nullable=False, comment='日志类型')
+    content = Column(Text, nullable=False, comment='日志内容')
+    ip = Column(String(45), nullable=True, comment='IP地址')
+    created_at = Column(TIMESTAMP, server_default=func.current_timestamp(),
+                        nullable=True, comment='记录时间')
diff --git a/Merge/front/package.json b/Merge/front/package.json
index f394aac..31c1d3e 100644
--- a/Merge/front/package.json
+++ b/Merge/front/package.json
@@ -13,7 +13,8 @@
     "react-scripts": "^5.0.1",
     "web-vitals": "^2.1.4",
     "lucide-react": "^0.468.0",
-    "antd": "^4.24.0"
+    "antd": "^4.24.0",
+    "crypto-js": "^4.2.0"
   },
   "scripts": {
     "start": "react-scripts start",
diff --git a/Merge/front/src/components/LogoutButton.js b/Merge/front/src/components/LogoutButton.js
new file mode 100644
index 0000000..a10e716
--- /dev/null
+++ b/Merge/front/src/components/LogoutButton.js
@@ -0,0 +1,77 @@
+import React from 'react';
+import { Button, Modal } from 'antd';
+import { LogoutOutlined } from '@ant-design/icons';
+import { clearAuthInfo, getUserInfo } from '../utils/auth';
+
+const LogoutButton = ({ style = {}, onLogout = null }) => {
+  const userInfo = getUserInfo();
+
+  const handleLogout = () => {
+    Modal.confirm({
+      title: '确认退出',
+      content: '您确定要退出登录吗?',
+      okText: '确定',
+      cancelText: '取消',
+      onOk: () => {
+        // 清除认证信息,但保留记住的登录信息
+        clearAuthInfo(false);
+        
+        // 执行回调函数
+        if (onLogout) {
+          onLogout();
+        } else {
+          // 默认跳转到登录页
+          window.location.href = '/';
+        }
+      }
+    });
+  };
+
+  const handleCompleteLogout = () => {
+    Modal.confirm({
+      title: '完全退出',
+      content: '这将清除所有保存的登录信息,包括"记住我"的设置。确定要继续吗?',
+      okText: '确定',
+      cancelText: '取消',
+      onOk: () => {
+        // 清除所有认证信息,包括记住的登录信息
+        clearAuthInfo(true);
+        
+        // 执行回调函数
+        if (onLogout) {
+          onLogout();
+        } else {
+          // 默认跳转到登录页
+          window.location.href = '/';
+        }
+      }
+    });
+  };
+
+  if (!userInfo) {
+    return null;
+  }
+
+  return (
+    <div style={style}>
+      <Button 
+        type="default" 
+        icon={<LogoutOutlined />}
+        onClick={handleLogout}
+        style={{ marginRight: 8 }}
+      >
+        退出登录
+      </Button>
+      <Button 
+        type="link" 
+        size="small"
+        onClick={handleCompleteLogout}
+        style={{ color: '#ff4d4f' }}
+      >
+        完全退出
+      </Button>
+    </div>
+  );
+};
+
+export default LogoutButton;
diff --git a/Merge/front/src/pages/ForgotPasswordPage/ForgotPasswordPage.css b/Merge/front/src/pages/ForgotPasswordPage/ForgotPasswordPage.css
new file mode 100644
index 0000000..6af35e6
--- /dev/null
+++ b/Merge/front/src/pages/ForgotPasswordPage/ForgotPasswordPage.css
@@ -0,0 +1,915 @@
+/* 忘记密码页面 - 继承注册页面样式 */
+
+/* 导入注册页面的所有样式 */
+@import url('../RegisterPage/RegisterPage.css');
+
+/* 小红书风格忘记密码卡片 */
+.forgot-password-card {
+  background: #fff;
+  border-radius: 8px;
+  padding: 40px; /* 增加桌面端内边距 */
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
+  border: 1px solid #e1e1e1;
+  width: 100%;
+  max-width: 450px; /* 增加桌面端卡片最大宽度 */
+  transition: none;
+}
+
+/* 忘记密码头部 */
+.forgot-password-header {
+  text-align: center;
+  margin-bottom: 40px;
+}
+
+/* Logo样式 */
+.logo-section {
+  margin-bottom: 24px;
+}
+
+.logo-icon {
+  width: 60px;
+  height: 60px;
+  background: #ff2442;
+  border-radius: 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 28px;
+  margin: 0 auto 16px;
+  box-shadow: 0 4px 12px rgba(255, 36, 66, 0.2);
+}
+
+.forgot-password-title {
+  font-size: 24px;
+  font-weight: 600;
+  color: #333;
+  margin: 0 0 12px 0;
+  text-align: center;
+}
+
+.forgot-password-title::after {
+  display: none;
+}
+
+.forgot-password-subtitle {
+  font-size: 14px;
+  color: #999;
+  margin: 0 0 32px 0;
+  font-weight: 400;
+  text-align: center;
+}
+
+/* 表单样式 */
+.forgot-password-form {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+  width: 100%;
+}
+
+.form-group {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+  width: 100%;
+  box-sizing: border-box;
+  margin-bottom: 2px;
+  position: relative; /* 为绝对定位的错误提示提供参考点 */
+}
+
+.form-group .input-wrapper {
+  width: 100%;
+  box-sizing: border-box;
+}
+
+.form-label {
+  font-size: 14px;
+  font-weight: 500;
+  color: #333;
+  margin-bottom: 8px;
+}
+
+.input-wrapper {
+  position: relative;
+  display: flex;
+  align-items: center;
+  width: 100%;
+  box-sizing: border-box;
+}
+
+.form-input {
+  width: 100% !important;
+  height: 44px;
+  padding: 12px 16px 12px 48px;
+  border: 1px solid #e1e1e1;
+  border-radius: 6px;
+  font-size: 14px;
+  transition: border-color 0.2s ease;
+  background: #fff;
+  color: #333;
+  box-sizing: border-box !important;
+  flex: 1;
+  min-width: 0;
+}
+
+/* 针对 Antd Input 组件的特定样式 */
+.form-input.ant-input,
+.form-input.ant-input-affix-wrapper {
+  width: 100% !important;
+  height: 44px !important;
+  border: 1px solid #e1e1e1 !important;
+  border-radius: 6px !important;
+  padding: 12px 48px 12px 48px !important;
+  font-size: 14px !important;
+  background: #fff !important;
+  color: #333 !important;
+  box-sizing: border-box !important;
+  flex: 1 !important;
+  min-width: 0 !important;
+  display: flex !important;
+  align-items: center !important;
+}
+
+.form-input.ant-input-affix-wrapper .ant-input {
+  width: 100% !important;
+  height: 100% !important;
+  padding: 0 !important;
+  border: none !important;
+  background: transparent !important;
+  flex: 1 !important;
+  min-width: 0 !important;
+  box-sizing: border-box !important;
+}
+
+.form-input:focus {
+  outline: none;
+  border-color: #ff2442;
+  box-shadow: none;
+  transform: none;
+}
+
+/* Antd Input focus 样式 */
+.form-input.ant-input:focus,
+.form-input.ant-input-affix-wrapper:focus,
+.form-input.ant-input-affix-wrapper-focused {
+  outline: none !important;
+  border-color: #ff2442 !important;
+  box-shadow: none !important;
+  transform: none !important;
+}
+
+.form-input::placeholder {
+  color: #9ca3af;
+}
+
+.input-icon {
+  position: absolute;
+  left: 16px;
+  top: 50%;
+  transform: translateY(-50%);
+  color: #9ca3af;
+  pointer-events: none;
+  transition: color 0.3s ease;
+  z-index: 2;
+}
+
+.form-input:focus + .input-icon {
+  color: #ff2442;
+}
+
+/* 邮箱验证码输入框容器 */
+.email-code-wrapper {
+  display: flex;
+  gap: 8px;
+  width: 100%;
+  box-sizing: border-box;
+  align-items: flex-start;
+}
+
+.email-code-input {
+  flex: 1;
+  min-width: 0;
+}
+
+.send-code-button {
+  height: 44px !important;
+  padding: 0 16px !important;
+  background: #ff2442 !important;
+  border-color: #ff2442 !important;
+  border-radius: 6px !important;
+  font-size: 14px !important;
+  font-weight: 500 !important;
+  white-space: nowrap !important;
+  flex-shrink: 0 !important;
+  min-width: 100px !important;
+  display: flex !important;
+  align-items: center !important;
+  justify-content: center !important;
+  transition: all 0.2s ease !important;
+  box-sizing: border-box !important;
+}
+
+.send-code-button:hover:not(:disabled) {
+  background: #d91e3a !important;
+  border-color: #d91e3a !important;
+  transform: none !important;
+  box-shadow: none !important;
+}
+
+.send-code-button:disabled {
+  background: #f5f5f5 !important;
+  border-color: #d9d9d9 !important;
+  color: #bfbfbf !important;
+  cursor: not-allowed !important;
+}
+
+.send-code-button.ant-btn-loading {
+  background: #ff2442 !important;
+  border-color: #ff2442 !important;
+  color: white !important;
+}
+
+/* 小红书风格忘记密码按钮 */
+.forgot-password-button {
+  width: 100%;
+  height: 48px; /* 固定高度,防止布局变化 */
+  padding: 12px;
+  background: #ff2442;
+  color: white;
+  border: none;
+  border-radius: 6px;
+  font-size: 14px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: background-color 0.2s ease;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  margin-top: 8px;
+  position: relative; /* 为绝对定位的加载状态做准备 */
+  box-sizing: border-box; /* 确保padding包含在总尺寸内 */
+  min-width: 0; /* 防止flex子元素造成宽度变化 */
+}
+
+.forgot-password-button:hover:not(:disabled) {
+  background: #d91e3a;
+  transform: none;
+  box-shadow: none;
+}
+
+.forgot-password-button:active:not(:disabled) {
+  transform: none;
+}
+
+.forgot-password-button:disabled {
+  background: #ccc;
+  cursor: not-allowed;
+  opacity: 0.8;
+}
+
+.forgot-password-button.loading {
+  background: #ff7b8a;
+  cursor: not-allowed;
+}
+
+.loading-spinner {
+  width: 16px;
+  height: 16px;
+  border: 2px solid rgba(255, 255, 255, 0.3);
+  border-radius: 50%;
+  border-top-color: #fff;
+  animation: spin 1s ease-in-out infinite;
+}
+
+@keyframes spin {
+  to { transform: rotate(360deg); }
+}
+
+/* 加载遮罩层 */
+.loading-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1000;
+  backdrop-filter: blur(4px);
+}
+
+.loading-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 16px;
+  background: white;
+  padding: 32px;
+  border-radius: 12px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+}
+
+.loading-spinner-large {
+  width: 40px;
+  height: 40px;
+  border: 4px solid rgba(255, 36, 66, 0.2);
+  border-radius: 50%;
+  border-top-color: #ff2442;
+  animation: spin 1s ease-in-out infinite;
+}
+
+.loading-text {
+  margin: 0;
+  color: #333;
+  font-size: 16px;
+  font-weight: 500;
+}
+
+/* 登录链接 */
+.login-link {
+  text-align: center;
+  margin-top: 20px;
+  padding-top: 20px;
+  border-top: 1px solid #e5e7eb;
+}
+
+.login-link p {
+  margin: 0 0 8px 0;
+  font-size: 14px;
+  color: #64748b;
+}
+
+.login-link p:last-child {
+  margin-bottom: 0;
+}
+
+.login-link a {
+  color: #ff2442;
+  text-decoration: none;
+  font-weight: 500;
+  transition: color 0.2s ease;
+}
+
+.login-link a:hover {
+  color: #d91e3a;
+  text-decoration: underline;
+}
+
+/* 返回邮箱输入样式 */
+.back-to-email {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  padding: 12px 16px;
+  background: #f8f8f8;
+  border-radius: 6px;
+  border: 1px solid #e1e1e1;
+}
+
+.back-button {
+  background: none;
+  border: none;
+  color: #ff2442;
+  font-size: 14px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  padding: 0;
+  transition: color 0.2s ease;
+  width: fit-content;
+}
+
+.back-button:hover {
+  color: #d91e3a;
+}
+
+.email-display {
+  font-size: 14px;
+  color: #666;
+  font-weight: 500;
+}
+
+/* 有左侧图标时的内边距调整 */
+.input-wrapper.has-icon .form-input {
+  padding-left: 48px !important;
+}
+
+.input-wrapper.has-icon .form-input.ant-input,
+.input-wrapper.has-icon .form-input.ant-input-affix-wrapper {
+  padding-left: 48px !important;
+}
+
+/* 有右侧切换按钮时的内边距调整 */
+.input-wrapper.has-toggle .form-input {
+  padding-right: 48px !important;
+}
+
+.input-wrapper.has-toggle .form-input.ant-input,
+.input-wrapper.has-toggle .form-input.ant-input-affix-wrapper {
+  padding-right: 48px !important;
+}
+
+/* 没有图标时的内边距调整 */
+.input-wrapper:not(.has-icon) .form-input {
+  padding-left: 16px !important;
+}
+
+.input-wrapper:not(.has-icon) .form-input.ant-input,
+.input-wrapper:not(.has-icon) .form-input.ant-input-affix-wrapper {
+  padding-left: 16px !important;
+}
+
+/* 没有切换按钮时的内边距调整 */
+.input-wrapper:not(.has-toggle) .form-input {
+  padding-right: 16px !important;
+}
+
+.input-wrapper:not(.has-toggle) .form-input.ant-input,
+.input-wrapper:not(.has-toggle) .form-input.ant-input-affix-wrapper {
+  padding-right: 16px !important;
+}
+
+/* 确保输入框内容完全填充 */
+.form-input.ant-input-affix-wrapper .ant-input-suffix {
+  position: absolute !important;
+  right: 12px !important;
+  top: 50% !important;
+  transform: translateY(-50%) !important;
+  margin: 0 !important;
+  padding: 0 !important;
+}
+
+.form-input.ant-input-affix-wrapper .ant-input-prefix {
+  position: absolute !important;
+  left: 16px !important;
+  top: 50% !important;
+  transform: translateY(-50%) !important;
+  margin: 0 !important;
+  padding: 0 !important;
+}
+
+/* 确保所有输入框完全填充其容器 */
+.form-group {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  width: 100%;
+  box-sizing: border-box;
+}
+
+.form-group .input-wrapper {
+  width: 100%;
+  box-sizing: border-box;
+}
+
+/* 防止输入框溢出容器 */
+.form-input,
+.form-input.ant-input,
+.form-input.ant-input-affix-wrapper {
+  max-width: 100% !important;
+  overflow: hidden !important;
+}
+
+/* 确保内部输入元素不会超出边界 */
+.form-input.ant-input-affix-wrapper .ant-input {
+  max-width: 100% !important;
+  overflow: hidden !important;
+  text-overflow: ellipsis !important;
+}
+
+/* 精细间距控制 */
+.forgot-password-header + .forgot-password-form {
+  margin-top: -4px;
+}
+
+.forgot-password-form .form-group:not(:last-child) {
+  margin-bottom: 2px;
+}
+
+.forgot-password-form .form-group:last-of-type {
+  margin-bottom: 6px;
+}
+
+.forgot-password-button + .login-link {
+  margin-top: 14px;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  /* 重置body和html确保一致性 */
+  html, body {
+    height: 100%;
+    height: 100dvh;
+    margin: 0;
+    padding: 0;
+    overflow-x: hidden;
+    box-sizing: border-box;
+  }
+  
+  .forgot-password-container {
+    padding: 16px;
+    align-items: center;
+    justify-content: center;
+    min-height: 100vh;
+    min-height: 100dvh; /* 动态视口高度 */
+    /* 强制重置可能影响定位的样式 */
+    margin: 0;
+    box-sizing: border-box;
+    /* 防止内容溢出影响布局 */
+    overflow-x: hidden;
+    overflow-y: auto;
+    /* 确保flexbox在所有移动设备上表现一致 */
+    -webkit-box-align: center;
+    -webkit-box-pack: center;
+    display: flex !important;
+    position: relative;
+  }
+  
+  .forgot-password-content {
+    max-width: 100%;
+    padding: 20px;
+    /* 确保内容区域稳定 */
+    margin: 0 auto;
+    box-sizing: border-box;
+    /* 防止宽度计算问题 */
+    width: calc(100% - 40px);
+    max-width: 480px; /* 增加最大宽度 */
+    position: relative;
+    display: flex;
+    justify-content: center;
+  }
+  
+  .forgot-password-card {
+    padding: 32px 28px; /* 增加内边距 */
+    border-radius: 16px;
+    /* 确保卡片稳定定位 */
+    margin: 0;
+    box-sizing: border-box;
+    width: 100%;
+    max-width: 450px; /* 增加卡片最大宽度 */
+    /* 防止backdrop-filter导致的渲染差异 */
+    will-change: auto;
+    position: relative;
+    /* 防止触摸操作影响布局 */
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    -webkit-tap-highlight-color: transparent;
+  }
+  
+  .forgot-password-title {
+    font-size: 24px;
+  }
+  
+  .form-input {
+    width: 100% !important;
+    height: 44px !important;
+    padding: 12px 48px 12px 48px;
+    font-size: 16px; /* 防止iOS Safari缩放 */
+    box-sizing: border-box !important;
+    flex: 1 !important;
+    min-width: 0 !important;
+  }
+  
+  .form-input.ant-input,
+  .form-input.ant-input-affix-wrapper {
+    width: 100% !important;
+    height: 44px !important;
+    padding: 12px 48px 12px 48px !important;
+    font-size: 16px !important;
+    box-sizing: border-box !important;
+    flex: 1 !important;
+    min-width: 0 !important;
+  }
+  
+  .form-input.ant-input-affix-wrapper .ant-input {
+    width: 100% !important;
+    height: 100% !important;
+    padding: 0 !important;
+    border: none !important;
+    background: transparent !important;
+    flex: 1 !important;
+    min-width: 0 !important;
+    box-sizing: border-box !important;
+  }
+}
+
+@media (max-width: 480px) {
+  .forgot-password-container {
+    padding: 16px;
+    align-items: center;
+    justify-content: center;
+    min-height: 100vh;
+    min-height: 100dvh; /* 动态视口高度 */
+    /* 强制重置样式 */
+    margin: 0;
+    box-sizing: border-box;
+    position: relative;
+    /* 确保垂直居中 */
+    display: flex !important;
+  }
+  
+  .forgot-password-content {
+    /* 更严格的尺寸控制 */
+    width: calc(100vw - 32px);
+    max-width: 420px; /* 增加最大宽度 */
+    padding: 16px;
+    margin: 0 auto;
+    box-sizing: border-box;
+    display: flex;
+    justify-content: center;
+  }
+  
+  .forgot-password-card {
+    padding: 28px 24px; /* 增加内边距 */
+    border-radius: 12px;
+    /* 确保卡片完全稳定 */
+    margin: 0;
+    box-sizing: border-box;
+    width: 100%;
+    position: relative;
+    /* 防止变换导致的位置偏移 */
+    transform: none !important;
+    /* 防止触摸操作影响布局 */
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    /* 防止点击时的高亮效果影响布局 */
+    -webkit-tap-highlight-color: transparent;
+  }
+  
+  .forgot-password-title {
+    font-size: 22px;
+  }
+  
+  .form-input {
+    width: 100% !important;
+    height: 44px !important;
+    padding: 12px 48px 12px 48px;
+    font-size: 16px; /* 防止iOS Safari缩放 */
+    box-sizing: border-box !important;
+    flex: 1 !important;
+    min-width: 0 !important;
+  }
+  
+  .form-input.ant-input,
+  .form-input.ant-input-affix-wrapper {
+    width: 100% !important;
+    height: 44px !important;
+    padding: 12px 48px 12px 48px !important;
+    font-size: 16px !important;
+    box-sizing: border-box !important;
+    flex: 1 !important;
+    min-width: 0 !important;
+  }
+  
+  .form-input.ant-input-affix-wrapper .ant-input {
+    width: 100% !important;
+    height: 100% !important;
+    padding: 0 !important;
+    border: none !important;
+    background: transparent !important;
+    flex: 1 !important;
+    min-width: 0 !important;
+    box-sizing: border-box !important;
+  }
+  
+  /* 移动端优化 */
+  .background-pattern {
+    display: none;
+  }
+  
+  /* 禁用可能影响位置的悬停效果 */
+  .forgot-password-card:hover {
+    transform: none !important;
+  }
+}
+
+/* 高对比度模式支持 */
+@media (prefers-contrast: high) {
+  .forgot-password-card {
+    background: white;
+    border: 2px solid #000;
+  }
+  
+  .form-input {
+    border-color: #000;
+  }
+  
+  .form-input:focus {
+    border-color: #0066cc;
+    box-shadow: 0 0 0 2px #0066cc;
+  }
+}
+
+/* 减少动画模式 */
+@media (prefers-reduced-motion: reduce) {
+  .background-pattern {
+    animation: none;
+  }
+  
+  .forgot-password-card,
+  .form-input,
+  .forgot-password-button {
+    transition: none;
+  }
+}
+
+/* 深色模式支持 */
+@media (prefers-color-scheme: dark) {
+  .forgot-password-background {
+    background: linear-gradient(135deg, #1a202c 0%, #2d3748 100%);
+  }
+  
+  .forgot-password-card {
+    background: rgba(26, 32, 44, 0.95);
+    border-color: rgba(255, 255, 255, 0.1);
+  }
+  
+  .forgot-password-title {
+    color: #f7fafc;
+  }
+  
+  .forgot-password-subtitle {
+    color: #a0aec0;
+  }
+  
+  .form-label {
+    color: #e2e8f0;
+  }
+  
+  .form-input {
+    background: #2d3748;
+    border-color: #4a5568;
+    color: #f7fafc;
+  }
+  
+  .form-input:focus {
+    border-color: #ff2442;
+  }
+  
+  .login-link {
+    border-color: #4a5568;
+  }
+  
+  .login-link p {
+    color: #a0aec0;
+  }
+  
+  /* 深色模式下的错误提示样式 */
+  .error-message {
+    background: rgba(26, 32, 44, 0.95);
+    color: #ff6b6b;
+  }
+  
+  .back-to-email {
+    background: #2d3748;
+    border-color: #4a5568;
+  }
+  
+  .email-display {
+    color: #a0aec0;
+  }
+}
+
+/* 错误提示样式 - 使用绝对定位避免影响布局 */
+.error-message {
+  position: absolute;
+  top: 95%;
+  left: 4px;
+  right: 4px;
+  font-size: 12px;
+  color: #ff4d4f;
+  margin-top: 4px;
+  display: flex;
+  align-items: center;
+  min-height: 16px;
+  animation: fadeInDown 0.3s ease-out;
+  font-weight: 400;
+  line-height: 1.2;
+  background: rgba(255, 255, 255, 0.95);
+  backdrop-filter: blur(4px);
+  padding: 2px 4px;
+  border-radius: 4px;
+  z-index: 10;
+  pointer-events: none; /* 避免干扰用户交互 */
+}
+
+@keyframes fadeInDown {
+  from {
+    opacity: 0;
+    transform: translateY(-8px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+/* 输入框错误状态样式 */
+.form-input.input-error,
+.form-input.input-error.ant-input,
+.form-input.input-error.ant-input-affix-wrapper {
+  border-color: #ff4d4f !important;
+  box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.1) !important;
+  transition: all 0.3s ease !important;
+}
+
+.form-input.input-error:focus,
+.form-input.input-error.ant-input:focus,
+.form-input.input-error.ant-input-affix-wrapper:focus,
+.form-input.input-error.ant-input-affix-wrapper-focused {
+  border-color: #ff4d4f !important;
+  box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2) !important;
+}
+
+/* 错误状态下的图标颜色 */
+.form-input.input-error .anticon {
+  color: #ff4d4f !important;
+}
+
+/* 确保表单组间距一致 */
+.form-group {
+  margin-bottom: 0px;
+}
+
+.form-group:last-of-type {
+  margin-bottom: 0px;
+}
+
+/* 错误弹窗样式 */
+.error-modal .ant-modal-header {
+  background: #fff;
+  border-bottom: 1px solid #f0f0f0;
+  padding: 16px 24px;
+}
+
+.error-modal .ant-modal-title {
+  color: #333;
+  font-weight: 600;
+  font-size: 16px;
+}
+
+.error-modal .ant-modal-body {
+  padding: 16px 24px 24px;
+}
+
+.error-modal .ant-modal-footer {
+  padding: 12px 24px 24px;
+  border-top: none;
+  text-align: center;
+}
+
+.error-modal .ant-btn-primary {
+  background: #ff2442;
+  border-color: #ff2442;
+  font-weight: 500;
+  height: 40px;
+  padding: 0 24px;
+  border-radius: 6px;
+  transition: all 0.2s ease;
+}
+
+.error-modal .ant-btn-primary:hover {
+  background: #d91e3a;
+  border-color: #d91e3a;
+  transform: translateY(-1px);
+  box-shadow: 0 4px 12px rgba(255, 36, 66, 0.3);
+}
+
+.error-modal .ant-modal-content {
+  border-radius: 12px;
+  overflow: hidden;
+  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+}
+
+/* 错误弹窗遮罩层 */
+.error-modal .ant-modal-mask {
+  background: rgba(0, 0, 0, 0.6);
+  backdrop-filter: blur(4px);
+}
+
+/* 错误弹窗动画 */
+.error-modal .ant-modal {
+  animation: errorModalSlideIn 0.3s ease-out;
+}
+
+@keyframes errorModalSlideIn {
+  from {
+    opacity: 0;
+    transform: translateY(-20px) scale(0.95);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0) scale(1);
+  }
+}
diff --git a/Merge/front/src/pages/ForgotPasswordPage/ForgotPasswordPage.js b/Merge/front/src/pages/ForgotPasswordPage/ForgotPasswordPage.js
new file mode 100644
index 0000000..d0437d5
--- /dev/null
+++ b/Merge/front/src/pages/ForgotPasswordPage/ForgotPasswordPage.js
@@ -0,0 +1,567 @@
+import React, { useState } from 'react';
+import { Link, useNavigate } from 'react-router-dom';
+import { Input, Button, message, Modal, Alert } from 'antd';
+import { MailOutlined, LockOutlined, SafetyOutlined, ExclamationCircleOutlined, CheckCircleOutlined } from '@ant-design/icons';
+import { hashPassword } from '../../utils/crypto';
+import './ForgotPasswordPage.css';
+
+const baseURL = 'http://10.126.59.25:8082';
+
+const ForgotPasswordPage = () => {
+  const [formData, setFormData] = useState({
+    email: '',
+    emailCode: '',
+    newPassword: '',
+    confirmPassword: ''
+  });
+
+  const [errors, setErrors] = useState({
+    email: '',
+    emailCode: '',
+    newPassword: '',
+    confirmPassword: ''
+  });
+
+  const [emailCodeSent, setEmailCodeSent] = useState(false);
+  const [countdown, setCountdown] = useState(0);
+  const [sendingCode, setSendingCode] = useState(false);
+  const [isLoading, setIsLoading] = useState(false);
+  const [errorModal, setErrorModal] = useState({
+    visible: false,
+    title: '',
+    content: ''
+  });
+  const [successAlert, setSuccessAlert] = useState({
+    visible: false,
+    message: ''
+  });
+  const [emailCodeSuccessAlert, setEmailCodeSuccessAlert] = useState({
+    visible: false,
+    message: ''
+  });
+
+  const navigate = useNavigate();
+
+  // 显示错误弹窗
+  const showErrorModal = (title, content) => {
+    setErrorModal({
+      visible: true,
+      title: title,
+      content: content
+    });
+  };
+
+  // 关闭错误弹窗
+  const closeErrorModal = () => {
+    setErrorModal({
+      visible: false,
+      title: '',
+      content: ''
+    });
+  };
+
+  // 显示成功提示
+  const showSuccessAlert = (message) => {
+    setSuccessAlert({
+      visible: true,
+      message: message
+    });
+    
+    // 3秒后自动隐藏
+    setTimeout(() => {
+      setSuccessAlert({
+        visible: false,
+        message: ''
+      });
+    }, 3000);
+  };
+
+  // 显示邮件验证码发送成功提示
+  const showEmailCodeSuccessAlert = (message) => {
+    setEmailCodeSuccessAlert({
+      visible: true,
+      message: message
+    });
+    
+    // 5秒后自动隐藏
+    setTimeout(() => {
+      setEmailCodeSuccessAlert({
+        visible: false,
+        message: ''
+      });
+    }, 5000);
+  };
+
+  // 倒计时效果
+  React.useEffect(() => {
+    let timer;
+    if (countdown > 0) {
+      timer = setTimeout(() => {
+        setCountdown(countdown - 1);
+      }, 1000);
+    }
+    return () => clearTimeout(timer);
+  }, [countdown]);
+
+  // 发送邮箱验证码
+  const sendEmailCode = async () => {
+    // 验证邮箱格式
+    if (!formData.email || typeof formData.email !== 'string' || !formData.email.trim()) {
+      setErrors(prev => ({
+        ...prev,
+        email: '请先输入邮箱地址'
+      }));
+      return;
+    }
+    
+    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
+      setErrors(prev => ({
+        ...prev,
+        email: '请输入有效的邮箱地址'
+      }));
+      return;
+    }
+
+    setSendingCode(true);
+    
+    try {
+      // 调用后端API发送验证码
+      const response = await fetch(baseURL + '/send-verification-code', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({
+          email: formData.email,
+          type: 'reset_password'
+        })
+      });
+      
+      if (!response.ok) {
+        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+      }
+      
+      const result = await response.json();
+      
+      if (result.success) {
+        showEmailCodeSuccessAlert('验证码已发送到您的邮箱');
+        setEmailCodeSent(true);
+        setCountdown(60); // 60秒倒计时
+        
+        // 清除邮箱错误提示
+        setErrors(prev => ({
+          ...prev,
+          email: ''
+        }));
+      } else {
+        // 根据具体错误信息进行处理
+        const errorMessage = result.message || '发送验证码失败,请稍后再试';
+        
+        if (errorMessage.includes('用户不存在') || errorMessage.includes('邮箱未注册')) {
+          setErrors(prev => ({
+            ...prev,
+            email: '该邮箱尚未注册,请检查邮箱地址或先注册账户'
+          }));
+        } else {
+          showErrorModal('发送验证码失败', errorMessage);
+        }
+      }
+      
+    } catch (error) {
+      console.error('发送验证码失败:', error);
+      
+      // 根据错误类型显示不同的错误信息
+      if (error.name === 'TypeError' && error.message.includes('Failed to fetch')) {
+        showErrorModal('网络连接失败', '无法连接到服务器,请检查您的网络连接后重试。');
+      } else if (error.message.includes('HTTP 500')) {
+        showErrorModal('服务器错误', '服务器出现了内部错误,请稍后重试。');
+      } else if (error.message.includes('HTTP 429')) {
+        showErrorModal('发送频率限制', '验证码发送过于频繁,请稍后再试。');
+      } else if (error.message.includes('HTTP 400')) {
+        showErrorModal('请求错误', '邮箱格式错误,请检查邮箱地址是否正确。');
+      } else {
+        showErrorModal('发送失败', '发送验证码失败,请稍后重试。');
+      }
+    } finally {
+      setSendingCode(false);
+    }
+  };
+
+  const handleInputChange = (field) => (e) => {
+    const value = e.target.value;
+    setFormData(prev => ({
+      ...prev,
+      [field]: value
+    }));
+    
+    // 清除对应字段的错误提示
+    if (errors[field]) {
+      setErrors(prev => ({
+        ...prev,
+        [field]: ''
+      }));
+    }
+  };
+
+  const handlePasswordChange = (e) => {
+    const value = e.target.value;
+    setFormData(prev => ({
+      ...prev,
+      newPassword: value
+    }));
+    
+    // 清除密码错误提示
+    if (errors.newPassword) {
+      setErrors(prev => ({
+        ...prev,
+        newPassword: ''
+      }));
+    }
+  };
+
+  const handleConfirmPasswordChange = (e) => {
+    const value = e.target.value;
+    setFormData(prev => ({
+      ...prev,
+      confirmPassword: value
+    }));
+    
+    // 清除确认密码错误提示
+    if (errors.confirmPassword) {
+      setErrors(prev => ({
+        ...prev,
+        confirmPassword: ''
+      }));
+    }
+  };
+
+  const validateForm = () => {
+    const newErrors = {
+      email: '',
+      emailCode: '',
+      newPassword: '',
+      confirmPassword: ''
+    };
+    
+    let hasError = false;
+    
+    // 验证邮箱
+    if (!formData.email || typeof formData.email !== 'string' || !formData.email.trim()) {
+      newErrors.email = '请输入邮箱地址';
+      hasError = true;
+    } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
+      newErrors.email = '请输入有效的邮箱地址';
+      hasError = true;
+    }
+    
+    // 验证邮箱验证码
+    if (!formData.emailCode || typeof formData.emailCode !== 'string' || !formData.emailCode.trim()) {
+      newErrors.emailCode = '请输入邮箱验证码';
+      hasError = true;
+    } else if (formData.emailCode.length !== 6 || !/^\d{6}$/.test(formData.emailCode)) {
+      newErrors.emailCode = '请输入6位数字验证码';
+      hasError = true;
+    }
+    
+    // 验证新密码
+    if (!formData.newPassword || typeof formData.newPassword !== 'string' || !formData.newPassword.trim()) {
+      newErrors.newPassword = '请输入新密码';
+      hasError = true;
+    } else if (formData.newPassword.length < 6) {
+      newErrors.newPassword = '密码长度至少6位';
+      hasError = true;
+    } else if (formData.newPassword.length > 20) {
+      newErrors.newPassword = '密码长度不能超过20位';
+      hasError = true;
+    }
+    
+    // 验证确认密码
+    if (!formData.confirmPassword || typeof formData.confirmPassword !== 'string' || !formData.confirmPassword.trim()) {
+      newErrors.confirmPassword = '请确认新密码';
+      hasError = true;
+    } else if (formData.newPassword !== formData.confirmPassword) {
+      newErrors.confirmPassword = '两次输入的密码不一致';
+      hasError = true;
+    }
+    
+    setErrors(newErrors);
+    return !hasError;
+  };
+
+  const handleSubmit = async (e) => {
+    e.preventDefault();
+    
+    // 验证表单
+    if (!validateForm()) {
+      return;
+    }
+    
+    setIsLoading(true);
+    
+    try {
+      // 验证码验证成功,重置密码
+      const resetResponse = await fetch(baseURL + '/reset-password', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({
+          email: formData.email,
+          new_password: hashPassword(formData.newPassword), // 前端加密密码
+          verification_code: hashPassword(formData.emailCode) // 前端加密验证码
+        })
+      });
+      
+      if (!resetResponse.ok) {
+        throw new Error(`HTTP ${resetResponse.status}: ${resetResponse.statusText}`);
+      }
+      
+      const resetResult = await resetResponse.json();
+      
+      if (resetResult.success) {
+        showSuccessAlert('密码重置成功!请使用新密码登录,正在跳转到登录页面...');
+        // 清空表单数据
+        setFormData({
+          email: '',
+          emailCode: '',
+          newPassword: '',
+          confirmPassword: ''
+        });
+        setErrors({
+          email: '',
+          emailCode: '',
+          newPassword: '',
+          confirmPassword: ''
+        });
+        // 延迟跳转到登录页面,让用户看到成功提示
+        setTimeout(() => {
+          navigate('/login');
+        }, 2000);
+      } else {
+        // 处理重置密码失败的情况
+        const errorMessage = resetResult.message || '密码重置失败,请稍后再试';
+        showErrorModal('密码重置失败', errorMessage);
+      }
+      
+    } catch (error) {
+      console.error('密码重置失败:', error);
+      
+      // 根据错误类型显示不同的错误信息
+      if (error.name === 'TypeError' && error.message.includes('Failed to fetch')) {
+        showErrorModal('网络连接失败', '无法连接到服务器,请检查您的网络连接后重试。');
+      } else if (error.message.includes('HTTP 500')) {
+        showErrorModal('服务器内部错误', '服务器出现了内部错误,请稍后重试。');
+      } else if (error.message.includes('HTTP 400')) {
+        showErrorModal('请求参数错误', '请求参数有误,请检查您输入的信息是否正确。');
+      } else if (error.message.includes('HTTP 409')) {
+        showErrorModal('操作冲突', '重置操作发生冲突,请稍后重试。');
+      } else {
+        showErrorModal('重置失败', '密码重置失败,请稍后重试。');
+      }
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  return (
+    <div className="register-container">
+      <div className="register-background"></div>
+      
+      {isLoading && (
+        <div className="loading-overlay">
+          <div className="loading-content">
+            <div className="loading-spinner-large"></div>
+            <p className="loading-text">正在重置密码...</p>
+          </div>
+        </div>
+      )}
+      
+      <div className="register-content">
+        <div className="register-card">
+          {/* 成功提示 */}
+          {successAlert.visible && (
+            <div style={{ marginBottom: '16px' }}>
+              <Alert
+                message={successAlert.message}
+                type="success"
+                icon={<CheckCircleOutlined />}
+                showIcon
+                closable
+                onClose={() => setSuccessAlert({ visible: false, message: '' })}
+                style={{
+                  borderRadius: '8px',
+                  border: '1px solid #b7eb8f',
+                  backgroundColor: '#f6ffed'
+                }}
+              />
+            </div>
+          )}
+
+          {/* 邮件验证码发送成功提示 */}
+          {emailCodeSuccessAlert.visible && (
+            <div style={{ marginBottom: '16px' }}>
+              <Alert
+                message={emailCodeSuccessAlert.message}
+                type="success"
+                icon={<CheckCircleOutlined />}
+                showIcon
+                closable
+                onClose={() => setEmailCodeSuccessAlert({ visible: false, message: '' })}
+                style={{
+                  borderRadius: '8px',
+                  border: '1px solid #b7eb8f',
+                  backgroundColor: '#f6ffed'
+                }}
+              />
+            </div>
+          )}
+          
+          <div className="register-header">
+            <h1 className="register-title">重置密码</h1>
+            <p className="register-subtitle">请输入邮箱地址和新密码</p>
+          </div>
+
+          <form className="register-form" onSubmit={handleSubmit}>
+            <div className="form-group">
+              <Input
+                type="email"
+                id="email"
+                name="email"
+                className={`form-input ${errors.email ? 'input-error' : ''}`}
+                placeholder="请输入邮箱地址"
+                value={formData.email}
+                onChange={handleInputChange('email')}
+                prefix={<MailOutlined />}
+                size="large"
+                title=""
+                status={errors.email ? 'error' : ''}
+              />
+              {errors.email && (
+                <div className="error-message">
+                  {errors.email}
+                </div>
+              )}
+            </div>
+
+            <div className="form-group">
+              <div className="email-code-wrapper">
+                <Input
+                  type="text"
+                  id="emailCode"
+                  name="emailCode"
+                  className={`form-input email-code-input ${errors.emailCode ? 'input-error' : ''}`}
+                  placeholder="请输入6位验证码"
+                  value={formData.emailCode}
+                  onChange={handleInputChange('emailCode')}
+                  prefix={<SafetyOutlined />}
+                  maxLength={6}
+                  size="large"
+                  title=""
+                  status={errors.emailCode ? 'error' : ''}
+                />
+                <Button
+                  type="primary"
+                  className="send-code-button"
+                  onClick={sendEmailCode}
+                  loading={sendingCode}
+                  disabled={countdown > 0 || !formData.email || sendingCode}
+                  size="large"
+                >
+                  {countdown > 0 ? `${countdown}s后重发` : (emailCodeSent ? '重新发送' : '发送验证码')}
+                </Button>
+              </div>
+              {errors.emailCode && (
+                <div className="error-message">
+                  {errors.emailCode}
+                </div>
+              )}
+            </div>
+
+            <div className="form-group">
+              <Input.Password
+                id="newPassword"
+                name="newPassword"
+                className={`form-input ${errors.newPassword ? 'input-error' : ''}`}
+                placeholder="请输入新密码"
+                value={formData.newPassword}
+                onChange={handlePasswordChange}
+                prefix={<LockOutlined />}
+                size="large"
+                title=""
+                status={errors.newPassword ? 'error' : ''}
+              />
+              {errors.newPassword && (
+                <div className="error-message">
+                  {errors.newPassword}
+                </div>
+              )}
+            </div>
+
+            <div className="form-group">
+              <Input.Password
+                id="confirmPassword"
+                name="confirmPassword"
+                className={`form-input ${errors.confirmPassword ? 'input-error' : ''}`}
+                placeholder="请确认新密码"
+                value={formData.confirmPassword}
+                onChange={handleConfirmPasswordChange}
+                prefix={<LockOutlined />}
+                size="large"
+                title=""
+                status={errors.confirmPassword ? 'error' : ''}
+              />
+              {errors.confirmPassword && (
+                <div className="error-message">
+                  {errors.confirmPassword}
+                </div>
+              )}
+            </div>
+
+            <button
+              type="submit"
+              className={`register-button ${isLoading ? 'loading' : ''}`}
+              disabled={isLoading}
+            >
+              {isLoading ? (
+                <>
+                  <div className="loading-spinner"></div>
+                  重置中...
+                </>
+              ) : (
+                '重置密码'
+              )}
+            </button>
+          </form>
+
+          <div className="login-link">
+            <p>想起密码了? <Link to="/login">立即登录</Link></p>
+            <p>还没有账户? <Link to="/register">立即注册</Link></p>
+          </div>
+        </div>
+      </div>
+
+      {/* 错误弹窗 */}
+      <Modal
+        title={
+          <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
+            <ExclamationCircleOutlined style={{ color: '#ff4d4f', fontSize: '18px' }} />
+            {errorModal.title}
+          </div>
+        }
+        open={errorModal.visible}
+        onOk={closeErrorModal}
+        onCancel={closeErrorModal}
+        okText="我知道了"
+        cancelButtonProps={{ style: { display: 'none' } }}
+        centered
+        className="error-modal"
+      >
+        <div style={{ padding: '16px 0', fontSize: '14px', lineHeight: '1.6' }}>
+          {errorModal.content}
+        </div>
+      </Modal>
+    </div>
+  );
+};
+
+export default ForgotPasswordPage;
diff --git a/Merge/front/src/pages/LoginPage/LoginPage.css b/Merge/front/src/pages/LoginPage/LoginPage.css
new file mode 100644
index 0000000..ab1e24c
--- /dev/null
+++ b/Merge/front/src/pages/LoginPage/LoginPage.css
@@ -0,0 +1,1288 @@
+/* 登录页面容器 */
+.login-container {
+  min-height: 100vh;
+  min-height: 100dvh; /* 动态视口高度,避免移动端地址栏影响 */
+  height: 100vh;
+  height: 100dvh;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  position: relative;
+  overflow: hidden;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+    sans-serif;
+  /* 确保容器稳定定位 */
+  box-sizing: border-box;
+  margin: 0;
+  padding: 0;
+  /* 重置文本对齐 */
+  text-align: initial;
+}
+
+/* 小红书风格背景 */
+.login-background {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: #f8f8f8;
+  z-index: -1;
+}
+
+/* 登录内容区域 */
+.login-content {
+  width: 100%;
+  max-width: 500px; /* 增加桌面端最大宽度 */
+  padding: 0;
+  z-index: 1;
+  /* 确保内容稳定定位 */
+  box-sizing: border-box;
+  position: relative;
+  display: flex;
+  justify-content: center;
+}
+
+/* 小红书风格登录卡片 */
+.login-card {
+  background: #fff;
+  border-radius: 8px;
+  padding: 40px; /* 增加桌面端内边距 */
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
+  border: 1px solid #e1e1e1;
+  width: 100%;
+  max-width: 450px; /* 增加桌面端卡片最大宽度 */
+  transition: none;
+}
+
+/* 登录头部 */
+.login-header {
+  text-align: center;
+  margin-bottom: 40px;
+}
+
+/* Logo样式 */
+.logo-section {
+  margin-bottom: 24px;
+}
+
+.logo-icon {
+  width: 60px;
+  height: 60px;
+  background: #ff2442;
+  border-radius: 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 28px;
+  margin: 0 auto 16px;
+  box-shadow: 0 4px 12px rgba(255, 36, 66, 0.2);
+}
+
+.login-title {
+  font-size: 24px;
+  font-weight: 600;
+  color: #333;
+  margin: 0 0 12px 0;
+  text-align: center;
+}
+
+.login-title::after {
+  display: none;
+}
+
+.login-subtitle {
+  font-size: 14px;
+  color: #999;
+  margin: 0 0 32px 0;
+  font-weight: 400;
+  text-align: center;
+}
+
+/* 表单样式 */
+.login-form {
+  display: flex;
+  flex-direction: column;
+  gap: 24px;
+  width: 100%;
+}
+
+.form-group {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+  width: 100%;
+  box-sizing: border-box;
+  margin-bottom: 2px;
+  position: relative; /* 为绝对定位的错误提示提供参考点 */
+}
+
+.form-group .input-wrapper {
+  width: 100%;
+  box-sizing: border-box;
+}
+
+.form-label {
+  font-size: 14px;
+  font-weight: 500;
+  color: #333;
+  margin-bottom: 8px;
+}
+
+.input-wrapper {
+  position: relative;
+  display: flex;
+  align-items: center;
+  width: 100%;
+  box-sizing: border-box;
+}
+
+.form-input {
+  width: 100% !important;
+  height: 44px;
+  padding: 12px 16px 12px 48px;
+  border: 1px solid #e1e1e1;
+  border-radius: 6px;
+  font-size: 14px;
+  transition: border-color 0.2s ease;
+  background: #fff;
+  color: #333;
+  box-sizing: border-box !important;
+  flex: 1;
+  min-width: 0;
+}
+
+/* 针对 Antd Input 组件的特定样式 */
+.form-input.ant-input,
+.form-input.ant-input-affix-wrapper {
+  width: 100% !important;
+  height: 44px !important;
+  border: 1px solid #e1e1e1 !important;
+  border-radius: 6px !important;
+  padding: 12px 48px 12px 48px !important;
+  font-size: 14px !important;
+  background: #fff !important;
+  color: #333 !important;
+  box-sizing: border-box !important;
+  flex: 1 !important;
+  min-width: 0 !important;
+  display: flex !important;
+  align-items: center !important;
+}
+
+.form-input.ant-input-affix-wrapper .ant-input {
+  width: 100% !important;
+  height: 100% !important;
+  padding: 0 !important;
+  border: none !important;
+  background: transparent !important;
+  flex: 1 !important;
+  min-width: 0 !important;
+  box-sizing: border-box !important;
+}
+
+.form-input:focus {
+  outline: none;
+  border-color: #ff2442;
+  box-shadow: none;
+  transform: none;
+}
+
+/* Antd Input focus 样式 */
+.form-input.ant-input:focus,
+.form-input.ant-input-affix-wrapper:focus,
+.form-input.ant-input-affix-wrapper-focused {
+  outline: none !important;
+  border-color: #ff2442 !important;
+  box-shadow: none !important;
+  transform: none !important;
+}
+
+.form-input::placeholder {
+  color: #9ca3af;
+}
+
+.input-icon {
+  position: absolute;
+  left: 16px;
+  top: 50%;
+  transform: translateY(-50%);
+  color: #9ca3af;
+  pointer-events: none;
+  transition: color 0.3s ease;
+  z-index: 2;
+}
+
+.form-input:focus + .input-icon {
+  color: #ff2442;
+}
+
+.password-toggle {
+  position: absolute;
+  right: 12px;
+  top: 50%;
+  transform: translateY(-50%);
+  background: none;
+  border: none;
+  color: #9ca3af;
+  cursor: pointer;
+  padding: 4px;
+  border-radius: 4px;
+  transition: all 0.3s ease;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 2;
+  width: 24px;
+  height: 24px;
+}
+
+.password-toggle:hover {
+  color: #ff2442;
+  background-color: rgba(255, 36, 66, 0.1);
+}
+
+/* 表单选项 */
+.form-options {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-top: 8px;
+  margin-bottom: 16px;
+  width: 100%;
+  flex-wrap: nowrap; /* 确保不换行 */
+  min-height: 24px;
+  gap: 8px; /* 添加基础间距 */
+}
+
+/* Ant Design Checkbox 样式兼容 */
+.form-options .ant-checkbox-wrapper {
+  flex: 0 0 auto; /* 不伸缩,保持原始大小 */
+  font-size: 14px;
+  color: #64748b;
+  white-space: nowrap; /* 防止文字换行 */
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 50%; /* 限制最大宽度 */
+}
+
+.form-options .ant-checkbox-wrapper .ant-checkbox {
+  margin-right: 8px;
+}
+
+.form-options .forgot-password {
+  flex: 0 0 auto; /* 不伸缩,保持原始大小 */
+  margin-left: auto;
+  white-space: nowrap;
+  color: #ff2442;
+  text-decoration: none;
+  font-size: 14px;
+  transition: color 0.3s ease;
+  max-width: 45%; /* 限制最大宽度 */
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.form-options .forgot-password:hover {
+  color: #ff1a3a;
+  text-decoration: underline;
+}
+
+.checkbox-wrapper {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  cursor: pointer;
+  font-size: 14px;
+  color: #64748b;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  flex-shrink: 0;
+}
+
+.checkbox {
+  position: relative;
+  width: 18px;
+  height: 18px;
+  margin: 0;
+  cursor: pointer;
+  opacity: 0;
+}
+
+.checkmark {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 18px;
+  height: 18px;
+  background-color: #fff;
+  border: 1px solid #e1e1e1;
+  border-radius: 3px;
+  transition: all 0.2s ease;
+}
+
+.checkbox:checked + .checkmark {
+  background-color: #ff2442;
+  border-color: #ff2442;
+}
+
+.checkmark:after {
+  content: "";
+  position: absolute;
+  display: none;
+  left: 5px;
+  top: 2px;
+  width: 4px;
+  height: 8px;
+  border: solid white;
+  border-width: 0 2px 2px 0;
+  transform: rotate(45deg);
+}
+
+.checkbox:checked + .checkmark:after {
+  display: block;
+}
+
+.forgot-password {
+  font-size: 14px;
+  color: #ff2442;
+  text-decoration: none;
+  font-weight: 400;
+  transition: color 0.2s ease;
+  margin-left: auto;
+  flex-shrink: 0;
+  white-space: nowrap;
+}
+
+.forgot-password:hover {
+  color: #d91e3a;
+  text-decoration: underline;
+}
+
+/* 小红书风格登录按钮 */
+.login-button {
+  width: 100%;
+  height: 48px; /* 固定高度,防止布局变化 */
+  padding: 12px;
+  background: #ff2442;
+  color: white;
+  border: none;
+  border-radius: 6px;
+  font-size: 14px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: background-color 0.2s ease;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  margin-top: 8px;
+  box-sizing: border-box; /* 确保padding包含在总尺寸内 */
+  min-width: 0; /* 防止flex子元素造成宽度变化 */
+}
+
+.login-button:hover:not(:disabled) {
+  background: #d91e3a;
+  transform: none;
+  box-shadow: none;
+}
+
+.login-button:active:not(:disabled) {
+  transform: none;
+}
+
+.login-button:disabled {
+  background: #ccc;
+  cursor: not-allowed;
+  opacity: 0.8;
+}
+
+.login-button.loading {
+  background: #ff7b8a;
+  cursor: not-allowed;
+}
+
+.loading-spinner {
+  width: 16px;
+  height: 16px;
+  border: 2px solid rgba(255, 255, 255, 0.3);
+  border-radius: 50%;
+  border-top-color: #fff;
+  animation: spin 1s ease-in-out infinite;
+}
+
+@keyframes spin {
+  to { transform: rotate(360deg); }
+}
+
+/* 加载遮罩层 */
+.loading-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1000;
+  backdrop-filter: blur(4px);
+}
+
+.loading-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 16px;
+  background: white;
+  padding: 32px;
+  border-radius: 12px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+}
+
+.loading-spinner-large {
+  width: 40px;
+  height: 40px;
+  border: 4px solid rgba(255, 36, 66, 0.2);
+  border-radius: 50%;
+  border-top-color: #ff2442;
+  animation: spin 1s ease-in-out infinite;
+}
+
+.loading-text {
+  margin: 0;
+  color: #333;
+  font-size: 16px;
+  font-weight: 500;
+}
+
+/* 分隔线 */
+.login-divider {
+  position: relative;
+  text-align: center;
+  margin: 32px 0;
+  color: #9ca3af;
+  font-size: 14px;
+}
+
+.login-divider::before {
+  content: '';
+  position: absolute;
+  top: 50%;
+  left: 0;
+  right: 0;
+  height: 1px;
+  background: linear-gradient(to right, transparent, #e5e7eb, transparent);
+}
+
+.login-divider span {
+  background: rgba(255, 255, 255, 0.95);
+  padding: 0 16px;
+  position: relative;
+  z-index: 1;
+}
+
+/* 社交登录 */
+.social-login {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.social-button {
+  width: 100%;
+  padding: 12px 16px;
+  border: 1px solid #e1e1e1;
+  border-radius: 6px;
+  background: white;
+  color: #333;
+  font-size: 14px;
+  font-weight: 400;
+  cursor: pointer;
+  transition: border-color 0.2s ease;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+}
+
+.social-button:hover {
+  border-color: #ccc;
+}
+
+.social-button.google:hover {
+  border-color: #4285f4;
+  box-shadow: 0 4px 12px rgba(66, 133, 244, 0.2);
+}
+
+.social-button.github:hover {
+  border-color: #333;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
+}
+
+.social-button.xiaohongshu:hover {
+  border-color: #ff2442;
+  box-shadow: 0 4px 12px rgba(255, 36, 66, 0.2);
+}
+
+/* 注册链接 */
+.signup-link {
+  text-align: center;
+  margin-top: 20px;
+  padding-top: 20px;
+  border-top: 1px solid #e5e7eb;
+}
+
+.signup-link p {
+  margin: 0;
+  font-size: 14px;
+  color: #64748b;
+}
+
+.signup-link a {
+  color: #ff2442;
+  text-decoration: none;
+  font-weight: 500;
+  transition: color 0.2s ease;
+}
+
+.signup-link a:hover {
+  color: #d91e3a;
+  text-decoration: underline;
+}
+
+/* 有左侧图标时的内边距调整 */
+.input-wrapper.has-icon .form-input {
+  padding-left: 48px !important;
+}
+
+.input-wrapper.has-icon .form-input.ant-input,
+.input-wrapper.has-icon .form-input.ant-input-affix-wrapper {
+  padding-left: 48px !important;
+}
+
+/* 有右侧切换按钮时的内边距调整 */
+.input-wrapper.has-toggle .form-input {
+  padding-right: 48px !important;
+}
+
+.input-wrapper.has-toggle .form-input.ant-input,
+.input-wrapper.has-toggle .form-input.ant-input-affix-wrapper {
+  padding-right: 48px !important;
+}
+
+/* 没有图标时的内边距调整 */
+.input-wrapper:not(.has-icon) .form-input {
+  padding-left: 16px !important;
+}
+
+.input-wrapper:not(.has-icon) .form-input.ant-input,
+.input-wrapper:not(.has-icon) .form-input.ant-input-affix-wrapper {
+  padding-left: 16px !important;
+}
+
+/* 没有切换按钮时的内边距调整 */
+.input-wrapper:not(.has-toggle) .form-input {
+  padding-right: 16px !important;
+}
+
+.input-wrapper:not(.has-toggle) .form-input.ant-input,
+.input-wrapper:not(.has-toggle) .form-input.ant-input-affix-wrapper {
+  padding-right: 16px !important;
+}
+
+/* 确保输入框内容完全填充 */
+.form-input.ant-input-affix-wrapper .ant-input-suffix {
+  position: absolute !important;
+  right: 12px !important;
+  top: 50% !important;
+  transform: translateY(-50%) !important;
+  margin: 0 !important;
+  padding: 0 !important;
+}
+
+.form-input.ant-input-affix-wrapper .ant-input-prefix {
+  position: absolute !important;
+  left: 16px !important;
+  top: 50% !important;
+  transform: translateY(-50%) !important;
+  margin: 0 !important;
+  padding: 0 !important;
+}
+
+/* 确保所有输入框完全填充其容器 */
+.form-group {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  width: 100%;
+  box-sizing: border-box;
+}
+
+.form-group .input-wrapper {
+  width: 100%;
+  box-sizing: border-box;
+}
+
+/* 防止输入框溢出容器 */
+.form-input,
+.form-input.ant-input,
+.form-input.ant-input-affix-wrapper {
+  max-width: 100% !important;
+  overflow: hidden !important;
+}
+
+/* 确保内部输入元素不会超出边界 */
+.form-input.ant-input-affix-wrapper .ant-input {
+  max-width: 100% !important;
+  overflow: hidden !important;
+  text-overflow: ellipsis !important;
+}
+
+/* 精细间距控制 */
+.login-header + .login-form {
+  margin-top: -4px;
+}
+
+.login-form .form-group:not(:last-child) {
+  margin-bottom: 2px;
+}
+
+.login-form .form-group:last-of-type {
+  margin-bottom: 6px;
+}
+
+.login-button + .signup-link {
+  margin-top: 14px;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  /* 重置body和html确保一致性 */
+  html, body {
+    height: 100%;
+    height: 100dvh;
+    margin: 0;
+    padding: 0;
+    overflow-x: hidden;
+    box-sizing: border-box;
+  }
+  
+  .login-container {
+    padding: 16px;
+    align-items: center;
+    justify-content: center;
+    min-height: 100vh;
+    min-height: 100dvh; /* 动态视口高度 */
+    /* 强制重置可能影响定位的样式 */
+    margin: 0;
+    box-sizing: border-box;
+    /* 防止内容溢出影响布局 */
+    overflow-x: hidden;
+    overflow-y: auto;
+    /* 确保flexbox在所有移动设备上表现一致 */
+    -webkit-box-align: center;
+    -webkit-box-pack: center;
+    display: flex !important;
+    position: relative;
+  }
+  
+  .login-content {
+    max-width: 100%;
+    padding: 20px;
+    /* 确保内容区域稳定 */
+    margin: 0 auto;
+    box-sizing: border-box;
+    /* 防止宽度计算问题 */
+    width: calc(100% - 40px);
+    max-width: 480px; /* 增加最大宽度 */
+    position: relative;
+    display: flex;
+    justify-content: center;
+  }
+  
+  .login-card {
+    padding: 32px 28px; /* 增加内边距 */
+    border-radius: 16px;
+    /* 确保卡片稳定定位 */
+    margin: 0;
+    box-sizing: border-box;
+    width: 100%;
+    max-width: 450px; /* 增加卡片最大宽度 */
+    /* 防止backdrop-filter导致的渲染差异 */
+    will-change: auto;
+    position: relative;
+    /* 防止触摸操作影响布局 */
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    -webkit-tap-highlight-color: transparent;
+  }
+  
+  .login-title {
+    font-size: 24px;
+  }
+  
+  .form-input {
+    width: 100% !important;
+    height: 44px !important;
+    padding: 12px 48px 12px 48px;
+    font-size: 16px; /* 防止iOS Safari缩放 */
+    box-sizing: border-box !important;
+    flex: 1 !important;
+    min-width: 0 !important;
+  }
+  
+  .form-input.ant-input,
+  .form-input.ant-input-affix-wrapper {
+    width: 100% !important;
+    height: 44px !important;
+    padding: 12px 48px 12px 48px !important;
+    font-size: 16px !important;
+    box-sizing: border-box !important;
+    flex: 1 !important;
+    min-width: 0 !important;
+  }
+  
+  .form-input.ant-input-affix-wrapper .ant-input {
+    width: 100% !important;
+    height: 100% !important;
+    padding: 0 !important;
+    border: none !important;
+    background: transparent !important;
+    flex: 1 !important;
+    min-width: 0 !important;
+    box-sizing: border-box !important;
+  }
+  
+  .social-login {
+    gap: 10px;
+  }
+  
+  .social-button {
+    padding: 12px 16px;
+    font-size: 14px;
+  }
+  
+  .form-options {
+    display: flex !important;
+    flex-direction: row !important;
+    justify-content: space-between !important;
+    align-items: center !important;
+    gap: 8px !important;
+    width: 100% !important;
+    flex-wrap: nowrap !important;
+    min-height: 22px !important;
+    margin-top: 8px !important;
+    margin-bottom: 16px !important;
+  }
+  
+  .checkbox-wrapper {
+    font-size: 13px;
+    flex: 0 0 auto !important;
+    white-space: nowrap !important;
+    overflow: hidden !important;
+    text-overflow: ellipsis !important;
+    max-width: 48% !important;
+  }
+  
+  .forgot-password {
+    font-size: 13px;
+    margin-left: auto;
+    flex: 0 0 auto !important;
+    white-space: nowrap !important;
+    overflow: hidden !important;
+    text-overflow: ellipsis !important;
+    max-width: 48% !important;
+  }
+  
+  /* Ant Design Checkbox 的特殊处理 */
+  .form-options .ant-checkbox-wrapper {
+    flex: 0 0 auto !important;
+    font-size: 13px !important;
+    white-space: nowrap !important;
+    overflow: hidden !important;
+    text-overflow: ellipsis !important;
+    max-width: 48% !important;
+  }
+}
+
+@media (max-width: 480px) {
+  .login-container {
+    padding: 16px;
+    align-items: center;
+    justify-content: center;
+    min-height: 100vh;
+    min-height: 100dvh; /* 动态视口高度 */
+    /* 强制重置样式 */
+    margin: 0;
+    box-sizing: border-box;
+    position: relative;
+    /* 确保垂直居中 */
+    display: flex !important;
+  }
+  
+  .login-content {
+    /* 更严格的尺寸控制 */
+    width: calc(100vw - 32px);
+    max-width: 420px; /* 增加最大宽度 */
+    padding: 16px;
+    margin: 0 auto;
+    box-sizing: border-box;
+    display: flex;
+    justify-content: center;
+  }
+  
+  .login-card {
+    padding: 28px 24px; /* 增加内边距 */
+    border-radius: 12px;
+    /* 确保卡片完全稳定 */
+    margin: 0;
+    box-sizing: border-box;
+    width: 100%;
+    position: relative;
+    /* 防止变换导致的位置偏移 */
+    transform: none !important;
+    /* 防止触摸操作影响布局 */
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    /* 防止点击时的高亮效果影响布局 */
+    -webkit-tap-highlight-color: transparent;
+  }
+  
+  .login-title {
+    font-size: 22px;
+  }
+  
+  .form-input {
+    width: 100% !important;
+    height: 44px !important;
+    padding: 12px 48px 12px 48px;
+    font-size: 16px; /* 防止iOS Safari缩放 */
+    box-sizing: border-box !important;
+    flex: 1 !important;
+    min-width: 0 !important;
+  }
+  
+  .form-input.ant-input,
+  .form-input.ant-input-affix-wrapper {
+    width: 100% !important;
+    height: 44px !important;
+    padding: 12px 48px 12px 48px !important;
+    font-size: 16px !important;
+    box-sizing: border-box !important;
+    flex: 1 !important;
+    min-width: 0 !important;
+  }
+  
+  .form-input.ant-input-affix-wrapper .ant-input {
+    width: 100% !important;
+    height: 100% !important;
+    padding: 0 !important;
+    border: none !important;
+    background: transparent !important;
+    flex: 1 !important;
+    min-width: 0 !important;
+    box-sizing: border-box !important;
+  }
+  
+  .social-login {
+    gap: 10px;
+  }
+  
+  .social-button {
+    padding: 12px 16px;
+    font-size: 14px;
+  }
+  
+  .form-options {
+    display: flex !important;
+    flex-direction: row !important;
+    justify-content: space-between !important;
+    align-items: center !important;
+    gap: 6px !important;
+    width: 100% !important;
+    min-height: 20px !important;
+    flex-wrap: nowrap !important;
+    margin-top: 8px !important;
+    margin-bottom: 16px !important;
+  }
+  
+  .checkbox-wrapper {
+    flex: 0 0 auto !important;
+    font-size: 12px !important;
+    white-space: nowrap !important;
+    overflow: hidden !important;
+    text-overflow: ellipsis !important;
+    max-width: 45% !important;
+  }
+  
+  .forgot-password {
+    flex: 0 0 auto !important;
+    font-size: 12px !important;
+    margin-left: auto !important;
+    white-space: nowrap !important;
+    overflow: hidden !important;
+    text-overflow: ellipsis !important;
+    max-width: 45% !important;
+  }
+  
+/* 移动端优化 */
+  .background-pattern {
+    display: none;
+  }
+  
+  /* 禁用可能影响位置的悬停效果 */
+  .login-card:hover {
+    transform: none !important;
+  }
+}
+
+/* 超小屏幕优化(320px及以下) */
+@media (max-width: 320px) {
+  .login-content {
+    padding: 16px;
+  }
+  
+  .login-card {
+    padding: 24px;
+  }
+  
+  .form-options {
+    display: flex !important;
+    flex-direction: row !important;
+    justify-content: space-between !important;
+    align-items: center !important;
+    gap: 4px !important;
+    width: 100% !important;
+    flex-wrap: nowrap !important;
+    min-height: 18px !important;
+    margin-top: 6px !important;
+    margin-bottom: 14px !important;
+  }
+  
+  .checkbox-wrapper {
+    flex: 0 0 auto !important;
+    font-size: 11px !important;
+    white-space: nowrap !important;
+    overflow: hidden !important;
+    text-overflow: ellipsis !important;
+    max-width: 42% !important;
+    line-height: 1.2 !important;
+  }
+  
+  .forgot-password {
+    flex: 0 0 auto !important;
+    font-size: 11px !important;
+    margin-left: auto !important;
+    white-space: nowrap !important;
+    overflow: hidden !important;
+    text-overflow: ellipsis !important;
+    max-width: 42% !important;
+    line-height: 1.2 !important;
+  }
+  
+  /* Ant Design Checkbox 的特殊处理 */
+  .form-options .ant-checkbox-wrapper {
+    flex: 0 0 auto !important;
+    font-size: 11px !important;
+    white-space: nowrap !important;
+    overflow: hidden !important;
+    text-overflow: ellipsis !important;
+    max-width: 42% !important;
+    line-height: 1.2 !important;
+  }
+}
+
+/* 极小屏幕优化(280px及以下) */
+@media (max-width: 280px) {
+  .form-options {
+    display: flex !important;
+    flex-direction: row !important;
+    justify-content: space-between !important;
+    align-items: center !important;
+    gap: 2px !important;
+    width: 100% !important;
+    flex-wrap: nowrap !important;
+    min-height: 16px !important;
+    margin-top: 6px !important;
+    margin-bottom: 14px !important;
+  }
+  
+  .form-options .ant-checkbox-wrapper {
+    flex: 0 0 auto !important;
+    font-size: 10px !important;
+    white-space: nowrap !important;
+    overflow: hidden !important;
+    text-overflow: ellipsis !important;
+    max-width: 40% !important;
+    line-height: 1.1 !important;
+  }
+  
+  .form-options .forgot-password {
+    flex: 0 0 auto !important;
+    font-size: 10px !important;
+    margin-left: auto !important;
+    white-space: nowrap !important;
+    overflow: hidden !important;
+    text-overflow: ellipsis !important;
+    max-width: 40% !important;
+    line-height: 1.1 !important;
+  }
+  
+  .form-options .ant-checkbox-wrapper .ant-checkbox {
+    margin-right: 2px !important;
+    transform: scale(0.8) !important; /* 进一步缩小checkbox */
+  }
+}
+
+/* 高对比度模式支持 */
+@media (prefers-contrast: high) {
+  .login-card {
+    background: white;
+    border: 2px solid #000;
+  }
+  
+  .form-input {
+    border-color: #000;
+  }
+  
+  .form-input:focus {
+    border-color: #0066cc;
+    box-shadow: 0 0 0 2px #0066cc;
+  }
+}
+
+/* 减少动画模式 */
+@media (prefers-reduced-motion: reduce) {
+  .background-pattern {
+    animation: none;
+  }
+  
+  .login-card,
+  .form-input,
+  .login-button,
+  .social-button {
+    transition: none;
+  }
+}
+
+/* 深色模式支持 */
+@media (prefers-color-scheme: dark) {
+  .login-background {
+    background: linear-gradient(135deg, #1a202c 0%, #2d3748 100%);
+  }
+  
+  .login-card {
+    background: rgba(26, 32, 44, 0.95);
+    border-color: rgba(255, 255, 255, 0.1);
+  }
+  
+  .login-title {
+    color: #f7fafc;
+  }
+  
+  .login-subtitle {
+    color: #a0aec0;
+  }
+  
+  .form-label {
+    color: #e2e8f0;
+  }
+  
+  .form-input {
+    background: #2d3748;
+    border-color: #4a5568;
+    color: #f7fafc;
+  }
+  
+  .form-input:focus {
+    border-color: #ff2442;
+  }
+  
+  .social-button {
+    background: #2d3748;
+    border-color: #4a5568;
+    color: #f7fafc;
+  }
+  
+  .signup-link {
+    border-color: #4a5568;
+  }
+  
+  .signup-link p {
+    color: #a0aec0;
+  }
+  
+  /* 深色模式下的错误提示样式 */
+  .error-message {
+    background: rgba(26, 32, 44, 0.95);
+    color: #ff6b6b;
+  }
+  
+  /* 深色模式下的错误弹窗样式 */
+  .error-modal .ant-modal-header {
+    background: #2d3748;
+    border-color: #4a5568;
+  }
+  
+  .error-modal .ant-modal-title {
+    color: #f7fafc;
+  }
+  
+  .error-modal .ant-modal-body {
+    background: #2d3748;
+    color: #f7fafc;
+  }
+  
+  .error-modal .ant-modal-footer {
+    background: #2d3748;
+  }
+  
+  .error-modal .ant-modal-content {
+    background: #2d3748;
+  }
+}
+
+/* 错误提示样式 - 使用绝对定位避免影响布局 */
+.error-message {
+  position: absolute;
+  top: 95%;
+  left: 4px;
+  right: 4px;
+  font-size: 12px;
+  color: #ff4d4f;
+  margin-top: 4px;
+  display: flex;
+  align-items: center;
+  min-height: 16px;
+  animation: fadeInDown 0.3s ease-out;
+  font-weight: 400;
+  line-height: 1.2;
+  background: rgba(255, 255, 255, 0.95);
+  backdrop-filter: blur(4px);
+  padding: 2px 4px;
+  border-radius: 4px;
+  z-index: 10;
+  pointer-events: none; /* 避免干扰用户交互 */
+}
+
+@keyframes fadeInDown {
+  from {
+    opacity: 0;
+    transform: translateY(-8px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+/* 输入框错误状态样式 */
+.form-input.input-error,
+.form-input.input-error.ant-input,
+.form-input.input-error.ant-input-affix-wrapper {
+  border-color: #ff4d4f !important;
+  box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.1) !important;
+  transition: all 0.3s ease !important;
+}
+
+.form-input.input-error:focus,
+.form-input.input-error.ant-input:focus,
+.form-input.input-error.ant-input-affix-wrapper:focus,
+.form-input.input-error.ant-input-affix-wrapper-focused {
+  border-color: #ff4d4f !important;
+  box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2) !important;
+}
+
+/* 错误状态下的图标颜色 */
+.form-input.input-error .anticon {
+  color: #ff4d4f !important;
+}
+
+/* 确保表单组间距一致 */
+.form-group {
+  margin-bottom: 0px;
+}
+
+.form-group:last-of-type {
+  margin-bottom: 0px;
+}
+
+/* 错误弹窗样式 */
+.error-modal .ant-modal-header {
+  background: #fff;
+  border-bottom: 1px solid #f0f0f0;
+  padding: 16px 24px;
+}
+
+.error-modal .ant-modal-title {
+  color: #333;
+  font-weight: 600;
+  font-size: 16px;
+}
+
+.error-modal .ant-modal-body {
+  padding: 16px 24px 24px;
+}
+
+.error-modal .ant-modal-footer {
+  padding: 12px 24px 24px;
+  border-top: none;
+  text-align: center;
+}
+
+.error-modal .ant-btn-primary {
+  background: #ff2442;
+  border-color: #ff2442;
+  font-weight: 500;
+  height: 40px;
+  padding: 0 24px;
+  border-radius: 6px;
+  transition: all 0.2s ease;
+}
+
+.error-modal .ant-btn-primary:hover {
+  background: #d91e3a;
+  border-color: #d91e3a;
+  transform: translateY(-1px);
+  box-shadow: 0 4px 12px rgba(255, 36, 66, 0.3);
+}
+
+.error-modal .ant-modal-content {
+  border-radius: 12px;
+  overflow: hidden;
+  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+}
+
+/* 错误弹窗遮罩层 */
+.error-modal .ant-modal-mask {
+  background: rgba(0, 0, 0, 0.6);
+  backdrop-filter: blur(4px);
+}
+
+/* 错误弹窗动画 */
+.error-modal .ant-modal {
+  animation: errorModalSlideIn 0.3s ease-out;
+}
+
+@keyframes errorModalSlideIn {
+  from {
+    opacity: 0;
+    transform: translateY(-20px) scale(0.95);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0) scale(1);
+  }
+}
diff --git a/Merge/front/src/pages/LoginPage/LoginPage.js b/Merge/front/src/pages/LoginPage/LoginPage.js
new file mode 100644
index 0000000..c315b7d
--- /dev/null
+++ b/Merge/front/src/pages/LoginPage/LoginPage.js
@@ -0,0 +1,380 @@
+import React, { useState, useEffect } from 'react';
+import { Link } from 'react-router-dom';
+import { Input, Checkbox, Modal, Alert } from 'antd';
+import { MailOutlined, LockOutlined, ExclamationCircleOutlined, CheckCircleOutlined } from '@ant-design/icons';
+import { 
+  getRememberedLoginInfo, 
+  saveRememberedLoginInfo, 
+  saveAuthInfo, 
+  isLoggedIn 
+} from '../../utils/auth';
+import { hashPassword } from '../../utils/crypto';
+import './LoginPage.css';
+
+const baseURL = 'http://10.126.59.25:8082';
+
+const LoginPage = () => {
+  const [formData, setFormData] = useState({
+    email: '',
+    password: ''
+  });
+
+  const [rememberMe, setRememberMe] = useState(false);
+  const [isLoading, setIsLoading] = useState(false);
+  const [errors, setErrors] = useState({
+    email: '',
+    password: ''
+  });
+  const [errorModal, setErrorModal] = useState({
+    visible: false,
+    title: '',
+    content: ''
+  });
+  const [successAlert, setSuccessAlert] = useState({
+    visible: false,
+    message: ''
+  });
+
+  // 显示错误弹窗
+  const showErrorModal = (title, content) => {
+    setErrorModal({
+      visible: true,
+      title: title,
+      content: content
+    });
+  };
+
+  // 关闭错误弹窗
+  const closeErrorModal = () => {
+    setErrorModal({
+      visible: false,
+      title: '',
+      content: ''
+    });
+  };
+
+  // 显示成功提示
+  const showSuccessAlert = (message) => {
+    setSuccessAlert({
+      visible: true,
+      message: message
+    });
+    
+    // 3秒后自动隐藏
+    setTimeout(() => {
+      setSuccessAlert({
+        visible: false,
+        message: ''
+      });
+    }, 3000);
+  };
+
+  // 页面加载时检查是否有记住的登录信息
+  useEffect(() => {
+    // 检查是否已经登录
+    if (isLoggedIn()) {
+      // 如果已经有token,可以选择直接跳转到主页面
+      // window.location.href = '/test-dashboard';
+      console.log('用户已登录');
+    }
+
+    // 获取记住的登录信息
+    const rememberedInfo = getRememberedLoginInfo();
+    if (rememberedInfo.rememberMe && rememberedInfo.email) {
+      setFormData({
+        email: rememberedInfo.email,
+        password: rememberedInfo.password
+      });
+      setRememberMe(true);
+    }
+  }, []);
+
+  const handleEmailChange = (e) => {
+    const value = e.target.value;
+    setFormData(prev => ({
+      ...prev,
+      email: value
+    }));
+    
+    // 清除邮箱错误提示
+    if (errors.email) {
+      setErrors(prev => ({
+        ...prev,
+        email: ''
+      }));
+    }
+  };
+
+  const handlePasswordChange = (e) => {
+    const value = e.target.value;
+    setFormData(prev => ({
+      ...prev,
+      password: value
+    }));
+    
+    // 清除密码错误提示
+    if (errors.password) {
+      setErrors(prev => ({
+        ...prev,
+        password: ''
+      }));
+    }
+  };
+
+  const handleRememberMeChange = (e) => {
+    const checked = e.target.checked;
+    setRememberMe(checked);
+    
+    // 如果取消记住我,清除已保存的登录信息
+    if (!checked) {
+      saveRememberedLoginInfo('', '', false);
+    }
+  };
+
+  const validateForm = () => {
+    const newErrors = {
+      email: '',
+      password: ''
+    };
+    
+    let hasError = false;
+    
+    // 验证邮箱
+    if (!formData.email || typeof formData.email !== 'string' || !formData.email.trim()) {
+      newErrors.email = '请输入邮箱地址';
+      hasError = true;
+    } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
+      newErrors.email = '请输入有效的邮箱地址';
+      hasError = true;
+    }
+    
+    // 验证密码
+    if (!formData.password || typeof formData.password !== 'string' || !formData.password.trim()) {
+      newErrors.password = '请输入密码';
+      hasError = true;
+    } else if (formData.password.length < 6) {
+      newErrors.password = '密码长度至少6位';
+      hasError = true;
+    }
+    
+    setErrors(newErrors);
+    return !hasError;
+  };
+
+  const handleSubmit = async (e) => {
+    e.preventDefault();
+    
+    // 验证表单
+    if (!validateForm()) {
+      return;
+    }
+    
+    setIsLoading(true);
+    
+    try {
+      // 发送登录请求到后端
+      const response = await fetch(baseURL + '/login', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({
+          email: formData.email, // 后端支持邮箱登录
+          password: hashPassword(formData.password) // 前端加密密码
+        })
+      });
+      
+      const result = await response.json();
+      
+      if (result.success) {
+        // 显示成功提示
+        showSuccessAlert('登录成功!正在跳转...');
+        
+        // 保存认证信息
+        saveAuthInfo(result.token, result.user, rememberMe);
+        
+        // 保存或清除记住的登录信息
+        saveRememberedLoginInfo(formData.email, formData.password, rememberMe);
+        
+        // 延迟跳转,让用户看到成功提示
+        setTimeout(() => {
+          window.location.href = '/test-dashboard';
+        }, 1500);
+      } else {
+        // 登录失败,显示错误信息
+        let errorTitle = '登录失败';
+        let errorContent = result.message || '登录失败,请检查您的邮箱和密码';
+        
+        // 根据错误类型提供更详细的信息
+        if (result.message) {
+          if (result.message.includes('邮箱') || result.message.includes('email')) {
+            errorTitle = '邮箱验证失败';
+            errorContent = '您输入的邮箱地址不存在或格式不正确,请检查后重试。';
+          } else if (result.message.includes('密码') || result.message.includes('password')) {
+            errorTitle = '密码验证失败';
+            errorContent = '您输入的密码不正确,请检查后重试。如果忘记密码,请点击"忘记密码"进行重置。';
+          } else if (result.message.includes('用户不存在')) {
+            errorTitle = '用户不存在';
+            errorContent = '该邮箱尚未注册,请先注册账户或检查邮箱地址是否正确。';
+          } else if (result.message.includes('账户被锁定') || result.message.includes('locked')) {
+            errorTitle = '账户被锁定';
+            errorContent = '您的账户因安全原因被暂时锁定,请联系客服或稍后重试。';
+          }
+        }
+        
+        showErrorModal(errorTitle, errorContent);
+      }
+    } catch (error) {
+      console.error('登录请求失败:', error);
+      
+      // 根据错误类型显示不同的错误信息
+      if (error.name === 'TypeError' && error.message.includes('fetch')) {
+        showErrorModal('网络连接失败', '无法连接到服务器,请检查您的网络连接后重试。如果问题持续存在,请联系客服。');
+      } else if (error.name === 'AbortError') {
+        showErrorModal('请求超时', '请求超时,请检查网络连接后重试。');
+      } else {
+        showErrorModal('登录失败', '网络连接失败,请检查网络或稍后重试。如果问题持续存在,请联系客服。');
+      }
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  return (
+    <div className="login-container">
+      <div className="login-background"></div>
+      
+      {isLoading && (
+        <div className="loading-overlay">
+          <div className="loading-content">
+            <div className="loading-spinner-large"></div>
+            <p className="loading-text">正在登录...</p>
+          </div>
+        </div>
+      )}
+      
+      <div className="login-content">
+        <div className="login-card">
+          {/* 成功提示 */}
+          {successAlert.visible && (
+            <div style={{ marginBottom: '16px' }}>
+              <Alert
+                message={successAlert.message}
+                type="success"
+                icon={<CheckCircleOutlined />}
+                showIcon
+                closable
+                onClose={() => setSuccessAlert({ visible: false, message: '' })}
+                style={{
+                  borderRadius: '8px',
+                  border: '1px solid #b7eb8f',
+                  backgroundColor: '#f6ffed'
+                }}
+              />
+            </div>
+          )}
+          
+          <div className="login-header">
+            <h1 className="login-title">欢迎来到小红书</h1>
+            <p className="login-subtitle">标记我的生活</p>
+          </div>
+
+          <form className="login-form" onSubmit={handleSubmit}>
+            <div className="form-group">
+              <Input
+                type="email"
+                id="email"
+                name="email"
+                className={`form-input ${errors.email ? 'input-error' : ''}`}
+                placeholder="请输入您的邮箱"
+                value={formData.email}
+                onChange={handleEmailChange}
+                prefix={<MailOutlined />}
+                size="large"
+                title=""
+                status={errors.email ? 'error' : ''}
+              />
+              {errors.email && (
+                <div className="error-message">
+                  {errors.email}
+                </div>
+              )}
+            </div>
+
+            <div className="form-group">
+              <Input.Password
+                id="password"
+                name="password"
+                className={`form-input ${errors.password ? 'input-error' : ''}`}
+                placeholder="请输入您的密码"
+                value={formData.password}
+                onChange={handlePasswordChange}
+                prefix={<LockOutlined />}
+                size="large"
+                title=""
+                status={errors.password ? 'error' : ''}
+              />
+              {errors.password && (
+                <div className="error-message">
+                  {errors.password}
+                </div>
+              )}
+            </div>
+
+            <div className="form-options">
+              <Checkbox 
+                checked={rememberMe}
+                onChange={handleRememberMeChange}
+              >
+                记住我
+              </Checkbox>
+              <Link to="/forgot-password" className="forgot-password">忘记密码?</Link>
+            </div>
+
+            <button
+              type="submit"
+              className={`login-button ${isLoading ? 'loading' : ''}`}
+              disabled={isLoading}
+            >
+              {isLoading ? (
+                <>
+                  <div className="loading-spinner"></div>
+                  登录中...
+                </>
+              ) : (
+                '登录'
+              )}
+            </button>
+          </form>
+
+          <div className="signup-link">
+            <p>还没有账户? <Link to="/register">立即注册</Link></p>
+          </div>
+        </div>
+      </div>
+
+      {/* 错误弹窗 */}
+      <Modal
+        title={
+          <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
+            <ExclamationCircleOutlined style={{ color: '#ff4d4f', fontSize: '18px' }} />
+            {errorModal.title}
+          </div>
+        }
+        open={errorModal.visible}
+        onOk={closeErrorModal}
+        onCancel={closeErrorModal}
+        okText="我知道了"
+        cancelButtonProps={{ style: { display: 'none' } }}
+        centered
+        className="error-modal"
+      >
+        <div style={{ padding: '16px 0', fontSize: '14px', lineHeight: '1.6' }}>
+          {errorModal.content}
+        </div>
+      </Modal>
+    </div>
+  );
+};
+
+export default LoginPage;
diff --git a/Merge/front/src/pages/RegisterPage/RegisterPage.css b/Merge/front/src/pages/RegisterPage/RegisterPage.css
new file mode 100644
index 0000000..fc03361
--- /dev/null
+++ b/Merge/front/src/pages/RegisterPage/RegisterPage.css
@@ -0,0 +1,1027 @@
+/* 注册页面容器 */
+.register-container {
+  min-height: 100vh;
+  min-height: 100dvh; /* 动态视口高度,避免移动端地址栏影响 */
+  height: 100vh;
+  height: 100dvh;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  position: relative;
+  overflow: hidden;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+    sans-serif;
+  /* 确保容器稳定定位 */
+  box-sizing: border-box;
+  margin: 0;
+  padding: 0;
+  /* 重置文本对齐 */
+  text-align: initial;
+}
+
+/* 小红书风格背景 */
+.register-background {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: #f8f8f8;
+  z-index: -1;
+}
+
+/* 注册内容区域 */
+.register-content {
+  width: 100%;
+  max-width: 500px; /* 增加桌面端最大宽度 */
+  padding: 0;
+  z-index: 1;
+  /* 确保内容稳定定位 */
+  box-sizing: border-box;
+  position: relative;
+  display: flex;
+  justify-content: center;
+}
+
+/* 小红书风格注册卡片 */
+.register-card {
+  background: #fff;
+  border-radius: 8px;
+  padding: 40px; /* 增加桌面端内边距 */
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
+  border: 1px solid #e1e1e1;
+  width: 100%;
+  max-width: 450px; /* 增加桌面端卡片最大宽度 */
+  transition: none;
+}
+
+/* 注册头部 */
+.register-header {
+  text-align: center;
+  margin-bottom: 40px;
+}
+
+/* Logo样式 */
+.logo-section {
+  margin-bottom: 24px;
+}
+
+.logo-icon {
+  width: 60px;
+  height: 60px;
+  background: #ff2442;
+  border-radius: 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 28px;
+  margin: 0 auto 16px;
+  box-shadow: 0 4px 12px rgba(255, 36, 66, 0.2);
+}
+
+.register-title {
+  font-size: 24px;
+  font-weight: 600;
+  color: #333;
+  margin: 0 0 12px 0;
+  text-align: center;
+}
+
+.register-title::after {
+  display: none;
+}
+
+.register-subtitle {
+  font-size: 14px;
+  color: #999;
+  margin: 0 0 32px 0;
+  font-weight: 400;
+  text-align: center;
+}
+
+/* 表单样式 */
+.register-form {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+  width: 100%;
+}
+
+.form-group {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+  width: 100%;
+  box-sizing: border-box;
+  margin-bottom: 2px;
+  position: relative; /* 为绝对定位的错误提示提供参考点 */
+}
+
+.form-group .input-wrapper {
+  width: 100%;
+  box-sizing: border-box;
+}
+
+.form-label {
+  font-size: 14px;
+  font-weight: 500;
+  color: #333;
+  margin-bottom: 8px;
+}
+
+.input-wrapper {
+  position: relative;
+  display: flex;
+  align-items: center;
+  width: 100%;
+  box-sizing: border-box;
+}
+
+.form-input {
+  width: 100% !important;
+  height: 44px;
+  padding: 12px 16px 12px 48px;
+  border: 1px solid #e1e1e1;
+  border-radius: 6px;
+  font-size: 14px;
+  transition: border-color 0.2s ease;
+  background: #fff;
+  color: #333;
+  box-sizing: border-box !important;
+  flex: 1;
+  min-width: 0;
+}
+
+/* 针对 Antd Input 组件的特定样式 */
+.form-input.ant-input,
+.form-input.ant-input-affix-wrapper {
+  width: 100% !important;
+  height: 44px !important;
+  border: 1px solid #e1e1e1 !important;
+  border-radius: 6px !important;
+  padding: 12px 48px 12px 48px !important;
+  font-size: 14px !important;
+  background: #fff !important;
+  color: #333 !important;
+  box-sizing: border-box !important;
+  flex: 1 !important;
+  min-width: 0 !important;
+  display: flex !important;
+  align-items: center !important;
+}
+
+.form-input.ant-input-affix-wrapper .ant-input {
+  width: 100% !important;
+  height: 100% !important;
+  padding: 0 !important;
+  border: none !important;
+  background: transparent !important;
+  flex: 1 !important;
+  min-width: 0 !important;
+  box-sizing: border-box !important;
+}
+
+.form-input:focus {
+  outline: none;
+  border-color: #ff2442;
+  box-shadow: none;
+  transform: none;
+}
+
+/* Antd Input focus 样式 */
+.form-input.ant-input:focus,
+.form-input.ant-input-affix-wrapper:focus,
+.form-input.ant-input-affix-wrapper-focused {
+  outline: none !important;
+  border-color: #ff2442 !important;
+  box-shadow: none !important;
+  transform: none !important;
+}
+
+.form-input::placeholder {
+  color: #9ca3af;
+}
+
+.input-icon {
+  position: absolute;
+  left: 16px;
+  top: 50%;
+  transform: translateY(-50%);
+  color: #9ca3af;
+  pointer-events: none;
+  transition: color 0.3s ease;
+  z-index: 2;
+}
+
+.form-input:focus + .input-icon {
+  color: #ff2442;
+}
+
+.password-toggle {
+  position: absolute;
+  right: 12px;
+  top: 50%;
+  transform: translateY(-50%);
+  background: none;
+  border: none;
+  color: #9ca3af;
+  cursor: pointer;
+  padding: 4px;
+  border-radius: 4px;
+  transition: all 0.3s ease;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 2;
+  width: 24px;
+  height: 24px;
+}
+
+.password-toggle:hover {
+  color: #ff2442;
+  background-color: rgba(255, 36, 66, 0.1);
+}
+
+/* 邮箱验证码输入框容器 */
+.email-code-wrapper {
+  display: flex;
+  gap: 8px;
+  width: 100%;
+  box-sizing: border-box;
+  align-items: flex-start;
+}
+
+.email-code-input {
+  flex: 1;
+  min-width: 0;
+}
+
+.send-code-button {
+  height: 44px !important;
+  padding: 0 16px !important;
+  background: #ff2442 !important;
+  border-color: #ff2442 !important;
+  border-radius: 6px !important;
+  font-size: 14px !important;
+  font-weight: 500 !important;
+  white-space: nowrap !important;
+  flex-shrink: 0 !important;
+  min-width: 100px !important;
+  display: flex !important;
+  align-items: center !important;
+  justify-content: center !important;
+  transition: all 0.2s ease !important;
+  box-sizing: border-box !important;
+}
+
+.send-code-button:hover:not(:disabled) {
+  background: #d91e3a !important;
+  border-color: #d91e3a !important;
+  transform: none !important;
+  box-shadow: none !important;
+}
+
+.send-code-button:disabled {
+  background: #f5f5f5 !important;
+  border-color: #d9d9d9 !important;
+  color: #bfbfbf !important;
+  cursor: not-allowed !important;
+}
+
+.send-code-button.ant-btn-loading {
+  background: #ff2442 !important;
+  border-color: #ff2442 !important;
+  color: white !important;
+}
+
+/* 小红书风格注册按钮 */
+.register-button {
+  width: 100%;
+  height: 48px; /* 固定高度,防止布局变化 */
+  padding: 12px;
+  background: #ff2442;
+  color: white;
+  border: none;
+  border-radius: 6px;
+  font-size: 14px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: background-color 0.2s ease;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  margin-top: 8px;
+  position: relative; /* 为绝对定位的加载状态做准备 */
+  box-sizing: border-box; /* 确保padding包含在总尺寸内 */
+  min-width: 0; /* 防止flex子元素造成宽度变化 */
+}
+
+.register-button:hover:not(:disabled) {
+  background: #d91e3a;
+  transform: none;
+  box-shadow: none;
+}
+
+.register-button:active:not(:disabled) {
+  transform: none;
+}
+
+.register-button:disabled {
+  background: #ccc;
+  cursor: not-allowed;
+  opacity: 0.8;
+}
+
+.register-button.loading {
+  background: #ff7b8a;
+  cursor: not-allowed;
+}
+
+.loading-spinner {
+  width: 16px;
+  height: 16px;
+  border: 2px solid rgba(255, 255, 255, 0.3);
+  border-radius: 50%;
+  border-top-color: #fff;
+  animation: spin 1s ease-in-out infinite;
+}
+
+@keyframes spin {
+  to { transform: rotate(360deg); }
+}
+
+/* 加载遮罩层 */
+.loading-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1000;
+  backdrop-filter: blur(4px);
+}
+
+.loading-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 16px;
+  background: white;
+  padding: 32px;
+  border-radius: 12px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+}
+
+.loading-spinner-large {
+  width: 40px;
+  height: 40px;
+  border: 4px solid rgba(255, 36, 66, 0.2);
+  border-radius: 50%;
+  border-top-color: #ff2442;
+  animation: spin 1s ease-in-out infinite;
+}
+
+.loading-text {
+  margin: 0;
+  color: #333;
+  font-size: 16px;
+  font-weight: 500;
+}
+
+/* 分隔线 */
+.register-divider {
+  position: relative;
+  text-align: center;
+  margin: 32px 0;
+  color: #9ca3af;
+  font-size: 14px;
+}
+
+.register-divider::before {
+  content: '';
+  position: absolute;
+  top: 50%;
+  left: 0;
+  right: 0;
+  height: 1px;
+  background: linear-gradient(to right, transparent, #e5e7eb, transparent);
+}
+
+.register-divider span {
+  background: rgba(255, 255, 255, 0.95);
+  padding: 0 16px;
+  position: relative;
+  z-index: 1;
+}
+
+/* 社交登录 */
+.social-login {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.social-button {
+  width: 100%;
+  padding: 12px 16px;
+  border: 1px solid #e1e1e1;
+  border-radius: 6px;
+  background: white;
+  color: #333;
+  font-size: 14px;
+  font-weight: 400;
+  cursor: pointer;
+  transition: border-color 0.2s ease;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+}
+
+.social-button:hover {
+  border-color: #ccc;
+}
+
+.social-button.google:hover {
+  border-color: #4285f4;
+  box-shadow: 0 4px 12px rgba(66, 133, 244, 0.2);
+}
+
+.social-button.github:hover {
+  border-color: #333;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
+}
+
+.social-button.xiaohongshu:hover {
+  border-color: #ff2442;
+  box-shadow: 0 4px 12px rgba(255, 36, 66, 0.2);
+}
+
+/* 登录链接 */
+.login-link {
+  text-align: center;
+  margin-top: 20px;
+  padding-top: 20px;
+  border-top: 1px solid #e5e7eb;
+}
+
+.login-link p {
+  margin: 0;
+  font-size: 14px;
+  color: #64748b;
+}
+
+.login-link a {
+  color: #ff2442;
+  text-decoration: none;
+  font-weight: 500;
+  transition: color 0.2s ease;
+}
+
+.login-link a:hover {
+  color: #d91e3a;
+  text-decoration: underline;
+}
+
+/* 有左侧图标时的内边距调整 */
+.input-wrapper.has-icon .form-input {
+  padding-left: 48px !important;
+}
+
+.input-wrapper.has-icon .form-input.ant-input,
+.input-wrapper.has-icon .form-input.ant-input-affix-wrapper {
+  padding-left: 48px !important;
+}
+
+/* 有右侧切换按钮时的内边距调整 */
+.input-wrapper.has-toggle .form-input {
+  padding-right: 48px !important;
+}
+
+.input-wrapper.has-toggle .form-input.ant-input,
+.input-wrapper.has-toggle .form-input.ant-input-affix-wrapper {
+  padding-right: 48px !important;
+}
+
+/* 没有图标时的内边距调整 */
+.input-wrapper:not(.has-icon) .form-input {
+  padding-left: 16px !important;
+}
+
+.input-wrapper:not(.has-icon) .form-input.ant-input,
+.input-wrapper:not(.has-icon) .form-input.ant-input-affix-wrapper {
+  padding-left: 16px !important;
+}
+
+/* 没有切换按钮时的内边距调整 */
+.input-wrapper:not(.has-toggle) .form-input {
+  padding-right: 16px !important;
+}
+
+.input-wrapper:not(.has-toggle) .form-input.ant-input,
+.input-wrapper:not(.has-toggle) .form-input.ant-input-affix-wrapper {
+  padding-right: 16px !important;
+}
+
+/* 确保输入框内容完全填充 */
+.form-input.ant-input-affix-wrapper .ant-input-suffix {
+  position: absolute !important;
+  right: 12px !important;
+  top: 50% !important;
+  transform: translateY(-50%) !important;
+  margin: 0 !important;
+  padding: 0 !important;
+}
+
+.form-input.ant-input-affix-wrapper .ant-input-prefix {
+  position: absolute !important;
+  left: 16px !important;
+  top: 50% !important;
+  transform: translateY(-50%) !important;
+  margin: 0 !important;
+  padding: 0 !important;
+}
+
+/* 确保所有输入框完全填充其容器 */
+.form-group {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  width: 100%;
+  box-sizing: border-box;
+}
+
+.form-group .input-wrapper {
+  width: 100%;
+  box-sizing: border-box;
+}
+
+/* 防止输入框溢出容器 */
+.form-input,
+.form-input.ant-input,
+.form-input.ant-input-affix-wrapper {
+  max-width: 100% !important;
+  overflow: hidden !important;
+}
+
+/* 确保内部输入元素不会超出边界 */
+.form-input.ant-input-affix-wrapper .ant-input {
+  max-width: 100% !important;
+  overflow: hidden !important;
+  text-overflow: ellipsis !important;
+}
+
+/* 精细间距控制 */
+.register-header + .register-form {
+  margin-top: -4px;
+}
+
+.register-form .form-group:not(:last-child) {
+  margin-bottom: 2px;
+}
+
+.register-form .form-group:last-of-type {
+  margin-bottom: 6px;
+}
+
+.register-button + .login-link {
+  margin-top: 14px;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  /* 重置body和html确保一致性 */
+  html, body {
+    height: 100%;
+    height: 100dvh;
+    margin: 0;
+    padding: 0;
+    overflow-x: hidden;
+    box-sizing: border-box;
+  }
+  
+  .register-container {
+    padding: 16px;
+    align-items: center;
+    justify-content: center;
+    min-height: 100vh;
+    min-height: 100dvh; /* 动态视口高度 */
+    /* 强制重置可能影响定位的样式 */
+    margin: 0;
+    box-sizing: border-box;
+    /* 防止内容溢出影响布局 */
+    overflow-x: hidden;
+    overflow-y: auto;
+    /* 确保flexbox在所有移动设备上表现一致 */
+    -webkit-box-align: center;
+    -webkit-box-pack: center;
+    display: flex !important;
+    position: relative;
+  }
+  
+  .register-content {
+    max-width: 100%;
+    padding: 20px;
+    /* 确保内容区域稳定 */
+    margin: 0 auto;
+    box-sizing: border-box;
+    /* 防止宽度计算问题 */
+    width: calc(100% - 40px);
+    max-width: 480px; /* 增加最大宽度 */
+    position: relative;
+    display: flex;
+    justify-content: center;
+  }
+  
+  .register-card {
+    padding: 32px 28px; /* 增加内边距 */
+    border-radius: 16px;
+    /* 确保卡片稳定定位 */
+    margin: 0;
+    box-sizing: border-box;
+    width: 100%;
+    max-width: 450px; /* 增加卡片最大宽度 */
+    /* 防止backdrop-filter导致的渲染差异 */
+    will-change: auto;
+    position: relative;
+    /* 防止触摸操作影响布局 */
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    -webkit-tap-highlight-color: transparent;
+  }
+  
+  .register-title {
+    font-size: 24px;
+  }
+  
+  .form-input {
+    width: 100% !important;
+    height: 44px !important;
+    padding: 12px 48px 12px 48px;
+    font-size: 16px; /* 防止iOS Safari缩放 */
+    box-sizing: border-box !important;
+    flex: 1 !important;
+    min-width: 0 !important;
+  }
+  
+  .form-input.ant-input,
+  .form-input.ant-input-affix-wrapper {
+    width: 100% !important;
+    height: 44px !important;
+    padding: 12px 48px 12px 48px !important;
+    font-size: 16px !important;
+    box-sizing: border-box !important;
+    flex: 1 !important;
+    min-width: 0 !important;
+  }
+  
+  .form-input.ant-input-affix-wrapper .ant-input {
+    width: 100% !important;
+    height: 100% !important;
+    padding: 0 !important;
+    border: none !important;
+    background: transparent !important;
+    flex: 1 !important;
+    min-width: 0 !important;
+    box-sizing: border-box !important;
+  }
+  
+  .social-login {
+    gap: 10px;
+  }
+  
+  .social-button {
+    padding: 12px 16px;
+    font-size: 14px;
+  }
+}
+
+@media (max-width: 480px) {
+  .register-container {
+    padding: 16px;
+    align-items: center;
+    justify-content: center;
+    min-height: 100vh;
+    min-height: 100dvh; /* 动态视口高度 */
+    /* 强制重置样式 */
+    margin: 0;
+    box-sizing: border-box;
+    position: relative;
+    /* 确保垂直居中 */
+    display: flex !important;
+  }
+  
+  .register-content {
+    /* 更严格的尺寸控制 */
+    width: calc(100vw - 32px);
+    max-width: 420px; /* 增加最大宽度 */
+    padding: 16px;
+    margin: 0 auto;
+    box-sizing: border-box;
+    display: flex;
+    justify-content: center;
+  }
+  
+  .register-card {
+    padding: 28px 24px; /* 增加内边距 */
+    border-radius: 12px;
+    /* 确保卡片完全稳定 */
+    margin: 0;
+    box-sizing: border-box;
+    width: 100%;
+    position: relative;
+    /* 防止变换导致的位置偏移 */
+    transform: none !important;
+    /* 防止触摸操作影响布局 */
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    /* 防止点击时的高亮效果影响布局 */
+    -webkit-tap-highlight-color: transparent;
+  }
+  
+  .register-title {
+    font-size: 22px;
+  }
+  
+  .form-input {
+    width: 100% !important;
+    height: 44px !important;
+    padding: 12px 48px 12px 48px;
+    font-size: 16px; /* 防止iOS Safari缩放 */
+    box-sizing: border-box !important;
+    flex: 1 !important;
+    min-width: 0 !important;
+  }
+  
+  .form-input.ant-input,
+  .form-input.ant-input-affix-wrapper {
+    width: 100% !important;
+    height: 44px !important;
+    padding: 12px 48px 12px 48px !important;
+    font-size: 16px !important;
+    box-sizing: border-box !important;
+    flex: 1 !important;
+    min-width: 0 !important;
+  }
+  
+  .form-input.ant-input-affix-wrapper .ant-input {
+    width: 100% !important;
+    height: 100% !important;
+    padding: 0 !important;
+    border: none !important;
+    background: transparent !important;
+    flex: 1 !important;
+    min-width: 0 !important;
+    box-sizing: border-box !important;
+  }
+  
+  .social-login {
+    gap: 10px;
+  }
+  
+  .social-button {
+    padding: 12px 16px;
+    font-size: 14px;
+  }
+  
+  /* 移动端优化 */
+  .background-pattern {
+    display: none;
+  }
+  
+  /* 禁用可能影响位置的悬停效果 */
+  .register-card:hover {
+    transform: none !important;
+  }
+}
+
+/* 高对比度模式支持 */
+@media (prefers-contrast: high) {
+  .register-card {
+    background: white;
+    border: 2px solid #000;
+  }
+  
+  .form-input {
+    border-color: #000;
+  }
+  
+  .form-input:focus {
+    border-color: #0066cc;
+    box-shadow: 0 0 0 2px #0066cc;
+  }
+}
+
+/* 减少动画模式 */
+@media (prefers-reduced-motion: reduce) {
+  .background-pattern {
+    animation: none;
+  }
+  
+  .register-card,
+  .form-input,
+  .register-button,
+  .social-button {
+    transition: none;
+  }
+}
+
+/* 深色模式支持 */
+@media (prefers-color-scheme: dark) {
+  .register-background {
+    background: linear-gradient(135deg, #1a202c 0%, #2d3748 100%);
+  }
+  
+  .register-card {
+    background: rgba(26, 32, 44, 0.95);
+    border-color: rgba(255, 255, 255, 0.1);
+  }
+  
+  .register-title {
+    color: #f7fafc;
+  }
+  
+  .register-subtitle {
+    color: #a0aec0;
+  }
+  
+  .form-label {
+    color: #e2e8f0;
+  }
+  
+  .form-input {
+    background: #2d3748;
+    border-color: #4a5568;
+    color: #f7fafc;
+  }
+  
+  .form-input:focus {
+    border-color: #ff2442;
+  }
+  
+  .social-button {
+    background: #2d3748;
+    border-color: #4a5568;
+    color: #f7fafc;
+  }
+  
+  .login-link {
+    border-color: #4a5568;
+  }
+  
+  .login-link p {
+    color: #a0aec0;
+  }
+  
+  /* 深色模式下的错误提示样式 */
+  .error-message {
+    background: rgba(26, 32, 44, 0.95);
+    color: #ff6b6b;
+  }
+}
+
+/* 错误提示样式 - 使用绝对定位避免影响布局 */
+.error-message {
+  position: absolute;
+  top: 95%;
+  left: 4px;
+  right: 4px;
+  font-size: 12px;
+  color: #ff4d4f;
+  margin-top: 4px;
+  display: flex;
+  align-items: center;
+  min-height: 16px;
+  animation: fadeInDown 0.3s ease-out;
+  font-weight: 400;
+  line-height: 1.2;
+  background: rgba(255, 255, 255, 0.95);
+  backdrop-filter: blur(4px);
+  padding: 2px 4px;
+  border-radius: 4px;
+  z-index: 10;
+  pointer-events: none; /* 避免干扰用户交互 */
+}
+
+@keyframes fadeInDown {
+  from {
+    opacity: 0;
+    transform: translateY(-8px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+/* 输入框错误状态样式 */
+.form-input.input-error,
+.form-input.input-error.ant-input,
+.form-input.input-error.ant-input-affix-wrapper {
+  border-color: #ff4d4f !important;
+  box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.1) !important;
+  transition: all 0.3s ease !important;
+}
+
+.form-input.input-error:focus,
+.form-input.input-error.ant-input:focus,
+.form-input.input-error.ant-input-affix-wrapper:focus,
+.form-input.input-error.ant-input-affix-wrapper-focused {
+  border-color: #ff4d4f !important;
+  box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2) !important;
+}
+
+/* 错误状态下的图标颜色 */
+.form-input.input-error .anticon {
+  color: #ff4d4f !important;
+}
+
+/* 确保表单组间距一致 */
+.form-group {
+  margin-bottom: 0px;
+}
+
+.form-group:last-of-type {
+  margin-bottom: 0px;
+}
+
+/* 错误弹窗样式 */
+.error-modal .ant-modal-header {
+  background: #fff;
+  border-bottom: 1px solid #f0f0f0;
+  padding: 16px 24px;
+}
+
+.error-modal .ant-modal-title {
+  color: #333;
+  font-weight: 600;
+  font-size: 16px;
+}
+
+.error-modal .ant-modal-body {
+  padding: 16px 24px 24px;
+}
+
+.error-modal .ant-modal-footer {
+  padding: 12px 24px 24px;
+  border-top: none;
+  text-align: center;
+}
+
+.error-modal .ant-btn-primary {
+  background: #ff2442;
+  border-color: #ff2442;
+  font-weight: 500;
+  height: 40px;
+  padding: 0 24px;
+  border-radius: 6px;
+  transition: all 0.2s ease;
+}
+
+.error-modal .ant-btn-primary:hover {
+  background: #d91e3a;
+  border-color: #d91e3a;
+  transform: translateY(-1px);
+  box-shadow: 0 4px 12px rgba(255, 36, 66, 0.3);
+}
+
+.error-modal .ant-modal-content {
+  border-radius: 12px;
+  overflow: hidden;
+  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+}
+
+/* 错误弹窗遮罩层 */
+.error-modal .ant-modal-mask {
+  background: rgba(0, 0, 0, 0.6);
+  backdrop-filter: blur(4px);
+}
+
+/* 错误弹窗动画 */
+.error-modal .ant-modal {
+  animation: errorModalSlideIn 0.3s ease-out;
+}
+
+@keyframes errorModalSlideIn {
+  from {
+    opacity: 0;
+    transform: translateY(-20px) scale(0.95);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0) scale(1);
+  }
+}
diff --git a/Merge/front/src/pages/RegisterPage/RegisterPage.js b/Merge/front/src/pages/RegisterPage/RegisterPage.js
new file mode 100644
index 0000000..836e1cf
--- /dev/null
+++ b/Merge/front/src/pages/RegisterPage/RegisterPage.js
@@ -0,0 +1,625 @@
+import React, { useState } from 'react';
+import { Link, useNavigate } from 'react-router-dom';
+import { Input, Button, message, Modal, Alert } from 'antd';
+import { MailOutlined, LockOutlined, UserOutlined, SafetyOutlined, ExclamationCircleOutlined, CheckCircleOutlined } from '@ant-design/icons';
+import { hashPassword } from '../../utils/crypto';
+import './RegisterPage.css';
+
+const baseURL = 'http://10.126.59.25:8082';
+
+const RegisterPage = () => {
+  const [formData, setFormData] = useState({
+    username: '',
+    email: '',
+    emailCode: '',
+    password: '',
+    confirmPassword: ''
+  });
+
+  const [errors, setErrors] = useState({
+    username: '',
+    email: '',
+    emailCode: '',
+    password: '',
+    confirmPassword: ''
+  });
+
+  const [emailCodeSent, setEmailCodeSent] = useState(false);
+  const [countdown, setCountdown] = useState(0);
+  const [sendingCode, setSendingCode] = useState(false);
+  const [isLoading, setIsLoading] = useState(false);
+  const [errorModal, setErrorModal] = useState({
+    visible: false,
+    title: '',
+    content: ''
+  });
+  const [successAlert, setSuccessAlert] = useState({
+    visible: false,
+    message: ''
+  });
+  const [emailCodeSuccessAlert, setEmailCodeSuccessAlert] = useState({
+    visible: false,
+    message: ''
+  });
+
+  const navigate = useNavigate();
+
+  // 显示错误弹窗
+  const showErrorModal = (title, content) => {
+    setErrorModal({
+      visible: true,
+      title: title,
+      content: content
+    });
+  };
+
+  // 关闭错误弹窗
+  const closeErrorModal = () => {
+    setErrorModal({
+      visible: false,
+      title: '',
+      content: ''
+    });
+  };
+
+  // 显示成功提示
+  const showSuccessAlert = (message) => {
+    setSuccessAlert({
+      visible: true,
+      message: message
+    });
+    
+    // 3秒后自动隐藏
+    setTimeout(() => {
+      setSuccessAlert({
+        visible: false,
+        message: ''
+      });
+    }, 3000);
+  };
+
+  // 显示邮件验证码发送成功提示
+  const showEmailCodeSuccessAlert = (message) => {
+    setEmailCodeSuccessAlert({
+      visible: true,
+      message: message
+    });
+    
+    // 5秒后自动隐藏
+    setTimeout(() => {
+      setEmailCodeSuccessAlert({
+        visible: false,
+        message: ''
+      });
+    }, 5000);
+  };
+
+  // 倒计时效果
+  React.useEffect(() => {
+    let timer;
+    if (countdown > 0) {
+      timer = setTimeout(() => {
+        setCountdown(countdown - 1);
+      }, 1000);
+    }
+    return () => clearTimeout(timer);
+  }, [countdown]);
+
+  // 发送邮箱验证码
+  const sendEmailCode = async () => {
+    // 验证邮箱格式
+    if (!formData.email || typeof formData.email !== 'string' || !formData.email.trim()) {
+      setErrors(prev => ({
+        ...prev,
+        email: '请先输入邮箱地址'
+      }));
+      return;
+    }
+    
+    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
+      setErrors(prev => ({
+        ...prev,
+        email: '请输入有效的邮箱地址'
+      }));
+      return;
+    }
+
+    setSendingCode(true);
+    
+    try {
+      // 调用后端API发送验证码
+      const response = await fetch(baseURL + '/send-verification-code', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({
+          email: formData.email,
+          verification_type: 'register'
+        })
+      });
+      
+      if (!response.ok) {
+        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+      }
+      
+      const result = await response.json();
+      
+      if (result.success) {
+        showEmailCodeSuccessAlert('验证码已发送到您的邮箱');
+        setEmailCodeSent(true);
+        setCountdown(60); // 60秒倒计时
+        
+        // 清除邮箱错误提示
+        setErrors(prev => ({
+          ...prev,
+          email: ''
+        }));
+      } else {
+        // 根据具体错误信息进行处理
+        const errorMessage = result.message || '发送验证码失败,请稍后再试';
+        
+        if (errorMessage.includes('邮箱') && (errorMessage.includes('已注册') || errorMessage.includes('已存在'))) {
+          setErrors(prev => ({
+            ...prev,
+            email: errorMessage
+          }));
+        } else {
+          showErrorModal('发送验证码失败', errorMessage);
+        }
+      }
+      
+    } catch (error) {
+      console.error('发送验证码失败:', error);
+      
+      // 根据错误类型显示不同的错误信息
+      if (error.name === 'TypeError' && error.message.includes('Failed to fetch')) {
+        showErrorModal('网络连接失败', '无法连接到服务器,请检查您的网络连接后重试。如果问题持续存在,请联系客服。');
+      } else if (error.message.includes('HTTP 500')) {
+        showErrorModal('服务器错误', '服务器出现了内部错误,请稍后重试。如果问题持续存在,请联系客服。');
+      } else if (error.message.includes('HTTP 429')) {
+        showErrorModal('发送频率限制', '验证码发送过于频繁,请稍后再试。为了防止垃圾邮件,系统限制了发送频率。');
+      } else if (error.message.includes('HTTP 400')) {
+        showErrorModal('请求错误', '邮箱格式错误,请检查邮箱地址是否正确。');
+      } else {
+        showErrorModal('发送失败', '发送验证码失败,请稍后重试。如果问题持续存在,请联系客服。');
+      }
+    } finally {
+      setSendingCode(false);
+    }
+  };
+
+  const handleInputChange = (field) => (e) => {
+    const value = e.target.value;
+    setFormData(prev => ({
+      ...prev,
+      [field]: value
+    }));
+    
+    // 清除对应字段的错误提示
+    if (errors[field]) {
+      setErrors(prev => ({
+        ...prev,
+        [field]: ''
+      }));
+    }
+  };
+
+  const handlePasswordChange = (e) => {
+    const value = e.target.value;
+    setFormData(prev => ({
+      ...prev,
+      password: value
+    }));
+    
+    // 清除密码错误提示
+    if (errors.password) {
+      setErrors(prev => ({
+        ...prev,
+        password: ''
+      }));
+    }
+  };
+
+  const handleConfirmPasswordChange = (e) => {
+    const value = e.target.value;
+    setFormData(prev => ({
+      ...prev,
+      confirmPassword: value
+    }));
+    
+    // 清除确认密码错误提示
+    if (errors.confirmPassword) {
+      setErrors(prev => ({
+        ...prev,
+        confirmPassword: ''
+      }));
+    }
+  };
+
+  const validateForm = () => {
+    const newErrors = {
+      username: '',
+      email: '',
+      emailCode: '',
+      password: '',
+      confirmPassword: ''
+    };
+    
+    let hasError = false;
+    
+    // 验证用户名
+    if (!formData.username || typeof formData.username !== 'string' || !formData.username.trim()) {
+      newErrors.username = '请输入用户名';
+      hasError = true;
+    } else if (formData.username.length < 2) {
+      newErrors.username = '用户名至少2个字符';
+      hasError = true;
+    } else if (formData.username.length > 20) {
+      newErrors.username = '用户名不能超过20个字符';
+      hasError = true;
+    }
+    
+    // 验证邮箱
+    if (!formData.email || typeof formData.email !== 'string' || !formData.email.trim()) {
+      newErrors.email = '请输入邮箱地址';
+      hasError = true;
+    } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
+      newErrors.email = '请输入有效的邮箱地址';
+      hasError = true;
+    }
+    
+    // 验证邮箱验证码
+    if (!formData.emailCode || typeof formData.emailCode !== 'string' || !formData.emailCode.trim()) {
+      newErrors.emailCode = '请输入邮箱验证码';
+      hasError = true;
+    } else if (formData.emailCode.length !== 6 || !/^\d{6}$/.test(formData.emailCode)) {
+      newErrors.emailCode = '请输入6位数字验证码';
+      hasError = true;
+    }
+    
+    // 验证密码
+    if (!formData.password || typeof formData.password !== 'string' || !formData.password.trim()) {
+      newErrors.password = '请输入密码';
+      hasError = true;
+    } else if (formData.password.length < 6) {
+      newErrors.password = '密码长度至少6位';
+      hasError = true;
+    } else if (formData.password.length > 20) {
+      newErrors.password = '密码长度不能超过20位';
+      hasError = true;
+    }
+    
+    // 验证确认密码
+    if (!formData.confirmPassword || typeof formData.confirmPassword !== 'string' || !formData.confirmPassword.trim()) {
+      newErrors.confirmPassword = '请确认密码';
+      hasError = true;
+    } else if (formData.password !== formData.confirmPassword) {
+      newErrors.confirmPassword = '两次输入的密码不一致';
+      hasError = true;
+    }
+    
+    setErrors(newErrors);
+    return !hasError;
+  };
+
+  const handleSubmit = async (e) => {
+    e.preventDefault();
+    
+    // 验证表单
+    if (!validateForm()) {
+      return;
+    }
+    
+    setIsLoading(true);
+    
+    try {
+      // 调用后端API进行注册
+      const registerResponse = await fetch(baseURL + '/register', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({
+          username: formData.username,
+          email: formData.email,
+          password: hashPassword(formData.password), // 前端加密密码
+          verification_code: hashPassword(formData.emailCode) // 前端加密验证码
+        })
+      });
+      
+      if (!registerResponse.ok) {
+        throw new Error(`HTTP ${registerResponse.status}: ${registerResponse.statusText}`);
+      }
+      
+      const registerResult = await registerResponse.json();
+      
+      if (registerResult.success) {
+        showSuccessAlert('注册成功!欢迎加入小红书,正在跳转到登录页面...');
+        // 清空表单数据
+        setFormData({
+          username: '',
+          email: '',
+          emailCode: '',
+          password: '',
+          confirmPassword: ''
+        });
+        setErrors({
+          username: '',
+          email: '',
+          emailCode: '',
+          password: '',
+          confirmPassword: ''
+        });
+        // 延迟跳转到登录页面,让用户看到成功提示
+        setTimeout(() => {
+          navigate('/login');
+        }, 2000);
+      } else {
+        // 处理不同的注册失败情况
+        const errorMessage = registerResult.message || '注册失败,请稍后再试';
+        
+        // 如果是邮箱已存在的错误,将错误显示在邮箱字段下方
+        if (errorMessage.includes('邮箱') && (errorMessage.includes('已存在') || errorMessage.includes('已注册'))) {
+          setErrors(prev => ({
+            ...prev,
+            email: errorMessage
+          }));
+        } 
+        // 如果是用户名已存在的错误,将错误显示在用户名字段下方
+        else if (errorMessage.includes('用户名') && (errorMessage.includes('已存在') || errorMessage.includes('已被使用'))) {
+          setErrors(prev => ({
+            ...prev,
+            username: errorMessage
+          }));
+        } 
+        else {
+          // 其他错误显示在消息框中
+          message.error(errorMessage);
+        }
+      }
+      
+    } catch (error) {
+      console.error('注册失败:', error);
+      
+      // 根据错误类型显示不同的错误信息
+      if (error.name === 'TypeError' && error.message.includes('Failed to fetch')) {
+        showErrorModal('网络连接失败', '无法连接到服务器,请检查您的网络连接后重试。如果问题持续存在,请联系客服。');
+      } else if (error.message.includes('HTTP 500')) {
+        showErrorModal('服务器内部错误', '服务器出现了内部错误,请稍后重试或联系客服。技术团队已收到通知。');
+      } else if (error.message.includes('HTTP 400')) {
+        showErrorModal('请求参数错误', '请求参数有误,请检查您输入的信息是否正确。');
+      } else if (error.message.includes('HTTP 409')) {
+        showErrorModal('用户信息冲突', '您输入的邮箱或用户名可能已被其他用户使用,请尝试使用其他邮箱或用户名。');
+      } else if (error.message.includes('HTTP')) {
+        showErrorModal('请求失败', `请求失败 (${error.message}),请稍后重试。如果问题持续存在,请联系客服。`);
+      } else {
+        showErrorModal('注册失败', '注册过程中发生未知错误,请稍后重试。如果问题持续存在,请联系客服。');
+      }
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  return (
+    <div className="register-container">
+      <div className="register-background"></div>
+      
+      {isLoading && (
+        <div className="loading-overlay">
+          <div className="loading-content">
+            <div className="loading-spinner-large"></div>
+            <p className="loading-text">正在注册...</p>
+          </div>
+        </div>
+      )}
+      
+      <div className="register-content">
+        <div className="register-card">
+          {/* 成功提示 */}
+          {successAlert.visible && (
+            <div style={{ marginBottom: '16px' }}>
+              <Alert
+                message={successAlert.message}
+                type="success"
+                icon={<CheckCircleOutlined />}
+                showIcon
+                closable
+                onClose={() => setSuccessAlert({ visible: false, message: '' })}
+                style={{
+                  borderRadius: '8px',
+                  border: '1px solid #b7eb8f',
+                  backgroundColor: '#f6ffed'
+                }}
+              />
+            </div>
+          )}
+
+          {/* 邮件验证码发送成功提示 */}
+          {emailCodeSuccessAlert.visible && (
+            <div style={{ marginBottom: '16px' }}>
+              <Alert
+                message={emailCodeSuccessAlert.message}
+                type="success"
+                icon={<CheckCircleOutlined />}
+                showIcon
+                closable
+                onClose={() => setEmailCodeSuccessAlert({ visible: false, message: '' })}
+                style={{
+                  borderRadius: '8px',
+                  border: '1px solid #b7eb8f',
+                  backgroundColor: '#f6ffed'
+                }}
+              />
+            </div>
+          )}
+          
+          <div className="register-header">
+            <h1 className="register-title">加入小红书</h1>
+            <p className="register-subtitle">发现美好生活,分享精彩瞬间</p>
+          </div>
+
+          <form className="register-form" onSubmit={handleSubmit}>
+            <div className="form-group">
+              <Input
+                type="text"
+                id="username"
+                name="username"
+                className={`form-input ${errors.username ? 'input-error' : ''}`}
+                placeholder="请输入用户名"
+                value={formData.username}
+                onChange={handleInputChange('username')}
+                prefix={<UserOutlined />}
+                size="large"
+                title=""
+                status={errors.username ? 'error' : ''}
+              />
+              {errors.username && (
+                <div className="error-message">
+                  {errors.username}
+                </div>
+              )}
+            </div>
+
+            <div className="form-group">
+              <Input
+                type="email"
+                id="email"
+                name="email"
+                className={`form-input ${errors.email ? 'input-error' : ''}`}
+                placeholder="请输入邮箱地址"
+                value={formData.email}
+                onChange={handleInputChange('email')}
+                prefix={<MailOutlined />}
+                size="large"
+                title=""
+                status={errors.email ? 'error' : ''}
+              />
+              {errors.email && (
+                <div className="error-message">
+                  {errors.email}
+                </div>
+              )}
+            </div>
+
+            <div className="form-group">
+              <div className="email-code-wrapper">
+                <Input
+                  type="text"
+                  id="emailCode"
+                  name="emailCode"
+                  className={`form-input email-code-input ${errors.emailCode ? 'input-error' : ''}`}
+                  placeholder="请输入6位验证码"
+                  value={formData.emailCode}
+                  onChange={handleInputChange('emailCode')}
+                  prefix={<SafetyOutlined />}
+                  maxLength={6}
+                  size="large"
+                  title=""
+                  status={errors.emailCode ? 'error' : ''}
+                />
+                <Button
+                  type="primary"
+                  className="send-code-button"
+                  onClick={sendEmailCode}
+                  loading={sendingCode}
+                  disabled={countdown > 0 || !formData.email || sendingCode}
+                  size="large"
+                >
+                  {countdown > 0 ? `${countdown}s后重发` : (emailCodeSent ? '重新发送' : '发送验证码')}
+                </Button>
+              </div>
+              {errors.emailCode && (
+                <div className="error-message">
+                  {errors.emailCode}
+                </div>
+              )}
+            </div>
+
+            <div className="form-group">
+              <Input.Password
+                id="password"
+                name="password"
+                className={`form-input ${errors.password ? 'input-error' : ''}`}
+                placeholder="请输入密码"
+                value={formData.password}
+                onChange={handlePasswordChange}
+                prefix={<LockOutlined />}
+                size="large"
+                title=""
+                status={errors.password ? 'error' : ''}
+              />
+              {errors.password && (
+                <div className="error-message">
+                  {errors.password}
+                </div>
+              )}
+            </div>
+
+            <div className="form-group">
+              <Input.Password
+                id="confirmPassword"
+                name="confirmPassword"
+                className={`form-input ${errors.confirmPassword ? 'input-error' : ''}`}
+                placeholder="请确认密码"
+                value={formData.confirmPassword}
+                onChange={handleConfirmPasswordChange}
+                prefix={<LockOutlined />}
+                size="large"
+                title=""
+                status={errors.confirmPassword ? 'error' : ''}
+              />
+              {errors.confirmPassword && (
+                <div className="error-message">
+                  {errors.confirmPassword}
+                </div>
+              )}
+            </div>
+
+            <button
+              type="submit"
+              className={`register-button ${isLoading ? 'loading' : ''}`}
+              disabled={isLoading}
+            >
+              {isLoading ? (
+                <>
+                  <div className="loading-spinner"></div>
+                  注册中...
+                </>
+              ) : (
+                '立即注册'
+              )}
+            </button>
+          </form>
+
+          <div className="login-link">
+            <p>已有账户? <Link to="/login">立即登录</Link></p>
+          </div>
+        </div>
+      </div>
+
+      {/* 错误弹窗 */}
+      <Modal
+        title={
+          <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
+            <ExclamationCircleOutlined style={{ color: '#ff4d4f', fontSize: '18px' }} />
+            {errorModal.title}
+          </div>
+        }
+        open={errorModal.visible}
+        onOk={closeErrorModal}
+        onCancel={closeErrorModal}
+        okText="我知道了"
+        cancelButtonProps={{ style: { display: 'none' } }}
+        centered
+        className="error-modal"
+      >
+        <div style={{ padding: '16px 0', fontSize: '14px', lineHeight: '1.6' }}>
+          {errorModal.content}
+        </div>
+      </Modal>
+    </div>
+  );
+};
+
+export default RegisterPage;
diff --git a/Merge/front/src/pages/TestDashboard/TestDashboard.css b/Merge/front/src/pages/TestDashboard/TestDashboard.css
new file mode 100644
index 0000000..a9e1c80
--- /dev/null
+++ b/Merge/front/src/pages/TestDashboard/TestDashboard.css
@@ -0,0 +1,189 @@
+.test-dashboard {
+  min-height: 100vh;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  padding: 20px;
+}
+
+.dashboard-header {
+  text-align: center;
+  margin-bottom: 30px;
+  color: white;
+}
+
+.dashboard-header h1 {
+  font-size: 2.5rem;
+  margin-bottom: 10px;
+  text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
+}
+
+.dashboard-header p {
+  font-size: 1.1rem;
+  opacity: 0.9;
+}
+
+.dashboard-content {
+  max-width: 1200px;
+  margin: 0 auto;
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
+
+.user-info-card,
+.token-info-card,
+.api-test-card {
+  background: white;
+  border-radius: 12px;
+  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
+  border: none;
+}
+
+.user-info-card .ant-card-head {
+  background: linear-gradient(90deg, #4f46e5 0%, #7c3aed 100%);
+  border-radius: 12px 12px 0 0;
+  border-bottom: none;
+}
+
+.user-info-card .ant-card-head-title {
+  color: white;
+  font-weight: 600;
+}
+
+.user-info-card .ant-card-extra .ant-btn {
+  color: white;
+  border-color: rgba(255, 255, 255, 0.3);
+}
+
+.user-info-card .ant-card-extra .ant-btn:hover {
+  background: rgba(255, 255, 255, 0.1);
+  border-color: rgba(255, 255, 255, 0.5);
+}
+
+.user-info-card .ant-card-extra .ant-btn-danger {
+  background: #ef4444;
+  border-color: #ef4444;
+}
+
+.user-info-card .ant-card-extra .ant-btn-danger:hover {
+  background: #dc2626;
+  border-color: #dc2626;
+}
+
+.token-info-card .ant-card-head {
+  background: linear-gradient(90deg, #059669 0%, #0d9488 100%);
+  border-radius: 12px 12px 0 0;
+  border-bottom: none;
+}
+
+.token-info-card .ant-card-head-title {
+  color: white;
+  font-weight: 600;
+}
+
+.api-test-card .ant-card-head {
+  background: linear-gradient(90deg, #ea580c 0%, #dc2626 100%);
+  border-radius: 12px 12px 0 0;
+  border-bottom: none;
+}
+
+.api-test-card .ant-card-head-title {
+  color: white;
+  font-weight: 600;
+}
+
+.token-display {
+  background: #f8fafc;
+  padding: 20px;
+  border-radius: 8px;
+  border: 1px solid #e2e8f0;
+}
+
+.token-text {
+  background: #1e293b;
+  color: #10b981;
+  padding: 12px;
+  border-radius: 6px;
+  font-family: 'Courier New', monospace;
+  font-size: 14px;
+  word-break: break-all;
+  margin: 10px 0;
+  border: 1px solid #334155;
+}
+
+.token-note {
+  color: #64748b;
+  font-size: 12px;
+  font-style: italic;
+  margin: 10px 0 0 0;
+}
+
+.loading-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 100vh;
+  color: white;
+}
+
+.spinner {
+  width: 40px;
+  height: 40px;
+  border: 4px solid rgba(255, 255, 255, 0.3);
+  border-left-color: white;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+  margin-bottom: 20px;
+}
+
+@keyframes spin {
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+.loading-container p {
+  font-size: 1.1rem;
+  opacity: 0.9;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .test-dashboard {
+    padding: 10px;
+  }
+  
+  .dashboard-header h1 {
+    font-size: 2rem;
+  }
+  
+  .dashboard-content {
+    gap: 15px;
+  }
+  
+  .user-info-card .ant-card-extra {
+    flex-direction: column;
+    gap: 8px;
+  }
+  
+  .token-text {
+    font-size: 12px;
+  }
+}
+
+/* Ant Design 组件样式覆写 */
+.ant-descriptions-item-label {
+  font-weight: 600;
+  color: #374151;
+  background: #f9fafb;
+}
+
+.ant-descriptions-item-content {
+  color: #111827;
+}
+
+.ant-tag {
+  font-weight: 500;
+  border-radius: 6px;
+  padding: 2px 8px;
+}
diff --git a/Merge/front/src/pages/TestDashboard/TestDashboard.js b/Merge/front/src/pages/TestDashboard/TestDashboard.js
new file mode 100644
index 0000000..0cc9914
--- /dev/null
+++ b/Merge/front/src/pages/TestDashboard/TestDashboard.js
@@ -0,0 +1,295 @@
+import React, { useState, useEffect } from 'react';
+import { Card, Button, Descriptions, Avatar, Tag, Space, message } from 'antd';
+import { UserOutlined, LogoutOutlined, ReloadOutlined } from '@ant-design/icons';
+import { getUserInfo, getAuthToken, isLoggedIn, saveAuthInfo, createAuthenticatedRequest } from '../../utils/auth';
+import LogoutButton from '../../components/LogoutButton';
+import './TestDashboard.css';
+
+const TestDashboard = () => {
+  const [userInfo, setUserInfo] = useState(null);
+  const [token, setToken] = useState(null);
+  const [loading, setLoading] = useState(false);
+  const [jwtTestLoading, setJwtTestLoading] = useState(false);
+
+  useEffect(() => {
+    // 检查用户是否已登录
+    if (!isLoggedIn()) {
+      window.location.href = '/';
+      return;
+    }
+
+    // 获取用户信息和token
+    const authToken = getAuthToken();
+    const authUserInfo = getUserInfo();
+    
+    setToken(authToken);
+    setUserInfo(authUserInfo);
+  }, []);
+
+  const handleRefreshProfile = async () => {
+    if (!token) {
+      message.error('未找到认证token');
+      return;
+    }
+
+    setLoading(true);
+    try {
+      const response = await fetch('http://10.126.59.25:8082/profile', createAuthenticatedRequest());
+
+      const result = await response.json();
+      
+      if (result.success) {
+        setUserInfo(result.user);
+        // 更新存储的用户信息,保持原有的存储方式(localStorage或sessionStorage)
+        const isRemembered = localStorage.getItem('authToken');
+        saveAuthInfo(token, result.user, !!isRemembered);
+        message.success('用户信息刷新成功');
+      } else {
+        message.error(`获取用户信息失败: ${result.message}`);
+      }
+    } catch (error) {
+      console.error('刷新用户信息失败:', error);
+      message.error('网络连接失败');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  const handleLogout = async () => {
+    if (!token) {
+      // 清除存储并跳转
+      localStorage.removeItem('authToken');
+      localStorage.removeItem('userInfo');
+      sessionStorage.removeItem('authToken');
+      sessionStorage.removeItem('userInfo');
+      window.location.href = '/';
+      return;
+    }
+
+    try {
+      const response = await fetch('http://10.126.59.25:8082/logout', {
+        method: 'POST',
+        headers: {
+          'Authorization': `Bearer ${token}`,
+          'Content-Type': 'application/json',
+        }
+      });
+
+      const result = await response.json();
+      
+      if (result.success) {
+        message.success('退出登录成功');
+      } else {
+        message.warning(`退出登录: ${result.message}`);
+      }
+    } catch (error) {
+      console.error('退出登录请求失败:', error);
+      message.warning('网络请求失败,但将清除本地数据');
+    } finally {
+      // 无论请求成功与否,都清除本地存储并跳转
+      localStorage.removeItem('authToken');
+      localStorage.removeItem('userInfo');
+      sessionStorage.removeItem('authToken');
+      sessionStorage.removeItem('userInfo');
+      window.location.href = '/';
+    }
+  };
+
+  const handleTestJWT = async () => {
+    if (!token) {
+      message.error('未找到认证token');
+      return;
+    }
+
+    setJwtTestLoading(true);
+    try {
+      const response = await fetch('http://10.126.59.25:8082/test-jwt', {
+        method: 'POST',
+        headers: {
+          'Authorization': `Bearer ${token}`,
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({
+          token: token, // 可选:在请求体中也发送token进行额外验证
+          test_purpose: 'frontend_jwt_test'
+        })
+      });
+
+      const result = await response.json();
+      
+      if (result.success) {
+        message.success(`JWT令牌验证成功!用户: ${result.user.username}`);
+        console.log('JWT验证详细结果:', result);
+        
+        // 如果有额外的token验证结果,也显示出来
+        if (result.additional_token_verification) {
+          console.log('额外token验证:', result.additional_token_verification);
+        }
+      } else {
+        message.error(`JWT令牌验证失败: ${result.message}`);
+      }
+    } catch (error) {
+      console.error('JWT令牌验证失败:', error);
+      message.error('网络连接失败');
+    } finally {
+      setJwtTestLoading(false);
+    }
+  };
+
+  const getRoleColor = (role) => {
+    switch (role) {
+      case 'superadmin':
+        return 'red';
+      case 'admin':
+        return 'orange';
+      case 'user':
+      default:
+        return 'blue';
+    }
+  };
+
+  const getStatusColor = (status) => {
+    switch (status) {
+      case 'active':
+        return 'green';
+      case 'banned':
+        return 'red';
+      case 'muted':
+        return 'orange';
+      default:
+        return 'default';
+    }
+  };
+
+  if (!userInfo) {
+    return (
+      <div className="test-dashboard">
+        <div className="loading-container">
+          <div className="spinner"></div>
+          <p>加载用户信息中...</p>
+        </div>
+      </div>
+    );
+  }
+
+  return (
+    <div className="test-dashboard">
+      <div className="dashboard-header">
+        <h1>测试仪表板</h1>
+        <p>登录成功!以下是从后端返回的用户信息:</p>
+      </div>
+
+      <div className="dashboard-content">
+        <Card
+          title={
+            <Space>
+              <Avatar size={40} icon={<UserOutlined />} src={userInfo.avatar} />
+              <span>用户信息</span>
+            </Space>
+          }
+          extra={
+            <Space>
+              <Button 
+                type="primary" 
+                icon={<ReloadOutlined />}
+                loading={loading}
+                onClick={handleRefreshProfile}
+              >
+                刷新信息
+              </Button>
+              <LogoutButton onLogout={() => window.location.href = '/'} />
+            </Space>
+          }
+          className="user-info-card"
+        >
+          <Descriptions column={2} bordered>
+            <Descriptions.Item label="用户ID">{userInfo.id}</Descriptions.Item>
+            <Descriptions.Item label="用户名">{userInfo.username}</Descriptions.Item>
+            <Descriptions.Item label="邮箱">{userInfo.email}</Descriptions.Item>
+            <Descriptions.Item label="角色">
+              <Tag color={getRoleColor(userInfo.role)}>
+                {userInfo.role}
+              </Tag>
+            </Descriptions.Item>
+            <Descriptions.Item label="账号状态">
+              <Tag color={getStatusColor(userInfo.status)}>
+                {userInfo.status}
+              </Tag>
+            </Descriptions.Item>
+            <Descriptions.Item label="个人简介" span={2}>
+              {userInfo.bio || '暂无个人简介'}
+            </Descriptions.Item>
+            <Descriptions.Item label="创建时间">
+              {userInfo.created_at ? new Date(userInfo.created_at).toLocaleString() : '未知'}
+            </Descriptions.Item>
+            <Descriptions.Item label="更新时间">
+              {userInfo.updated_at ? new Date(userInfo.updated_at).toLocaleString() : '未知'}
+            </Descriptions.Item>
+          </Descriptions>
+        </Card>
+
+        <Card title="登录状态信息" className="login-status-card">
+          <div className="login-status-display">
+            <Descriptions column={1} bordered>
+              <Descriptions.Item label="登录方式">
+                <Tag color={localStorage.getItem('authToken') ? 'green' : 'blue'}>
+                  {localStorage.getItem('authToken') ? '记住我登录 (持久化)' : '普通登录 (会话)'}
+                </Tag>
+              </Descriptions.Item>
+              <Descriptions.Item label="Token存储位置">
+                {localStorage.getItem('authToken') ? 'localStorage (浏览器关闭后仍保持登录)' : 'sessionStorage (浏览器关闭后需重新登录)'}
+              </Descriptions.Item>
+              <Descriptions.Item label="记住的登录信息">
+                {localStorage.getItem('rememberMe') === 'true' ? 
+                  `已保存邮箱: ${localStorage.getItem('rememberedEmail') || '无'}` : 
+                  '未保存登录信息'
+                }
+              </Descriptions.Item>
+            </Descriptions>
+          </div>
+        </Card>
+
+        <Card title="Token信息" className="token-info-card">
+          <div className="token-display">
+            <p><strong>认证Token:</strong></p>
+            <div className="token-text">
+              {token ? `${token.substring(0, 50)}...` : '未找到token'}
+            </div>
+            <p className="token-note">
+              * Token已被安全截断显示,完整token存储在浏览器存储中
+            </p>
+          </div>
+        </Card>
+
+        <Card title="API测试" className="api-test-card">
+          <Space direction="vertical" style={{ width: '100%' }}>
+            <p>您可以使用以下按钮测试不同的API接口:</p>
+            <Space wrap>
+              <Button onClick={handleRefreshProfile} loading={loading}>
+                测试 GET /profile
+              </Button>
+              <Button onClick={handleLogout}>
+                测试 POST /logout
+              </Button>
+              <Button 
+                onClick={handleTestJWT} 
+                loading={jwtTestLoading}
+                type="primary"
+              >
+                测试 POST /test-jwt
+              </Button>
+              <Button 
+                type="dashed"
+                onClick={() => window.open('http://10.126.59.25:8082/health', '_blank')}
+              >
+                测试 GET /health
+              </Button>
+            </Space>
+          </Space>
+        </Card>
+      </div>
+    </div>
+  );
+};
+
+export default TestDashboard;
diff --git a/Merge/front/src/reportWebVitals.js b/Merge/front/src/reportWebVitals.js
new file mode 100644
index 0000000..5253d3a
--- /dev/null
+++ b/Merge/front/src/reportWebVitals.js
@@ -0,0 +1,13 @@
+const reportWebVitals = onPerfEntry => {
+  if (onPerfEntry && onPerfEntry instanceof Function) {
+    import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
+      getCLS(onPerfEntry);
+      getFID(onPerfEntry);
+      getFCP(onPerfEntry);
+      getLCP(onPerfEntry);
+      getTTFB(onPerfEntry);
+    });
+  }
+};
+
+export default reportWebVitals;
diff --git a/Merge/front/src/router/App.js b/Merge/front/src/router/App.js
index 1a7fe0e..e1b9454 100644
--- a/Merge/front/src/router/App.js
+++ b/Merge/front/src/router/App.js
@@ -15,6 +15,11 @@
 import UploadPage     from '../components/UploadPage'      // src/components/UploadPage.jsx
 
 
+import LoginPage from '../pages/LoginPage/LoginPage';
+import RegisterPage from '../pages/RegisterPage/RegisterPage';
+import ForgotPasswordPage from '../pages/ForgotPasswordPage/ForgotPasswordPage';
+import TestDashboard from '../pages/TestDashboard/TestDashboard';
+
 export default function AppRoutes() {
   return (
     <Routes>
@@ -31,7 +36,13 @@
       <Route path="/dashboard/*" element={<UploadPage />} />
 
       {/* 根路径重定向到 dashboard */}
-      <Route path="/" element={<Navigate to="/dashboard/overview" replace />} />
+      {/* <Route path="/" element={<Navigate to="/dashboard/overview" replace />} /> */}
+
+      <Route path="/" element={<LoginPage />} />
+      <Route path="/login" element={<LoginPage />} />
+      <Route path="/register" element={<RegisterPage />} />
+      <Route path="/forgot-password" element={<ForgotPasswordPage />} />
+      <Route path="/test-dashboard" element={<TestDashboard />} />
 
       {/* 最后一个兜底 */}
       <Route path="*" element={<PlaceholderPage pageId="home" />} />
diff --git a/Merge/front/src/utils/auth.js b/Merge/front/src/utils/auth.js
new file mode 100644
index 0000000..d04e102
--- /dev/null
+++ b/Merge/front/src/utils/auth.js
@@ -0,0 +1,155 @@
+// 认证相关的工具函数
+
+/**
+ * 获取当前用户的认证token
+ * @returns {string|null} 认证token,如果未登录则返回null
+ */
+export const getAuthToken = () => {
+  // 优先从localStorage获取(记住我的情况)
+  const localToken = localStorage.getItem('authToken');
+  if (localToken) {
+    return localToken;
+  }
+  
+  // 然后从sessionStorage获取(不记住我的情况)
+  const sessionToken = sessionStorage.getItem('authToken');
+  return sessionToken;
+};
+
+/**
+ * 获取当前用户信息
+ * @returns {object|null} 用户信息,如果未登录则返回null
+ */
+export const getUserInfo = () => {
+  // 优先从localStorage获取
+  const localUserInfo = localStorage.getItem('userInfo');
+  if (localUserInfo) {
+    try {
+      return JSON.parse(localUserInfo);
+    } catch (error) {
+      console.error('解析localStorage中的用户信息失败:', error);
+    }
+  }
+  
+  // 然后从sessionStorage获取
+  const sessionUserInfo = sessionStorage.getItem('userInfo');
+  if (sessionUserInfo) {
+    try {
+      return JSON.parse(sessionUserInfo);
+    } catch (error) {
+      console.error('解析sessionStorage中的用户信息失败:', error);
+    }
+  }
+  
+  return null;
+};
+
+/**
+ * 检查用户是否已登录
+ * @returns {boolean} 是否已登录
+ */
+export const isLoggedIn = () => {
+  const token = getAuthToken();
+  return !!token;
+};
+
+/**
+ * 获取记住的登录信息
+ * @returns {object} 包含email, password, rememberMe的对象
+ */
+export const getRememberedLoginInfo = () => {
+  const email = localStorage.getItem('rememberedEmail') || '';
+  const password = localStorage.getItem('rememberedPassword') || '';
+  const rememberMe = localStorage.getItem('rememberMe') === 'true';
+  
+  return {
+    email,
+    password,
+    rememberMe
+  };
+};
+
+/**
+ * 保存记住的登录信息
+ * @param {string} email 邮箱
+ * @param {string} password 密码
+ * @param {boolean} remember 是否记住
+ */
+export const saveRememberedLoginInfo = (email, password, remember) => {
+  if (remember) {
+    localStorage.setItem('rememberedEmail', email);
+    localStorage.setItem('rememberedPassword', password);
+    localStorage.setItem('rememberMe', 'true');
+  } else {
+    localStorage.removeItem('rememberedEmail');
+    localStorage.removeItem('rememberedPassword');
+    localStorage.removeItem('rememberMe');
+  }
+};
+
+/**
+ * 保存用户认证信息
+ * @param {string} token 认证token
+ * @param {object} userInfo 用户信息
+ * @param {boolean} remember 是否记住登录状态
+ */
+export const saveAuthInfo = (token, userInfo, remember = false) => {
+  if (remember) {
+    // 记住我:保存到localStorage
+    localStorage.setItem('authToken', token);
+    localStorage.setItem('userInfo', JSON.stringify(userInfo));
+    
+    // 清除sessionStorage
+    sessionStorage.removeItem('authToken');
+    sessionStorage.removeItem('userInfo');
+  } else {
+    // 不记住我:保存到sessionStorage
+    sessionStorage.setItem('authToken', token);
+    sessionStorage.setItem('userInfo', JSON.stringify(userInfo));
+    
+    // 清除localStorage中的认证信息(但保留记住的登录表单信息)
+    localStorage.removeItem('authToken');
+    localStorage.removeItem('userInfo');
+  }
+};
+
+/**
+ * 清除所有认证信息(退出登录)
+ * @param {boolean} clearRemembered 是否同时清除记住的登录信息
+ */
+export const clearAuthInfo = (clearRemembered = false) => {
+  // 清除认证token和用户信息
+  localStorage.removeItem('authToken');
+  localStorage.removeItem('userInfo');
+  sessionStorage.removeItem('authToken');
+  sessionStorage.removeItem('userInfo');
+  
+  // 如果需要,清除记住的登录信息
+  if (clearRemembered) {
+    localStorage.removeItem('rememberedEmail');
+    localStorage.removeItem('rememberedPassword');
+    localStorage.removeItem('rememberMe');
+  }
+};
+
+/**
+ * 创建带认证头的fetch请求配置
+ * @param {object} options 原始fetch配置
+ * @returns {object} 带认证头的fetch配置
+ */
+export const createAuthenticatedRequest = (options = {}) => {
+  const token = getAuthToken();
+  
+  if (!token) {
+    throw new Error('用户未登录');
+  }
+  
+  return {
+    ...options,
+    headers: {
+      ...options.headers,
+      'Authorization': `Bearer ${token}`,
+      'Content-Type': 'application/json'
+    }
+  };
+};
diff --git a/Merge/front/src/utils/crypto.js b/Merge/front/src/utils/crypto.js
new file mode 100644
index 0000000..eac10f0
--- /dev/null
+++ b/Merge/front/src/utils/crypto.js
@@ -0,0 +1,48 @@
+// 密码加密工具函数
+import CryptoJS from 'crypto-js';
+
+/**
+ * 使用 SHA256 加密密码
+ * @param {string} password 原始密码
+ * @returns {string} 加密后的密码
+ */
+export const hashPassword = (password) => {
+  if (!password || typeof password !== 'string') {
+    throw new Error('密码必须是非空字符串');
+  }
+  
+  return CryptoJS.SHA256(password).toString();
+};
+
+/**
+ * 验证密码是否已经被加密
+ * @param {string} password 密码字符串
+ * @returns {boolean} 是否为已加密的密码(64位十六进制字符串)
+ */
+export const isEncryptedPassword = (password) => {
+  if (!password || typeof password !== 'string') {
+    return false;
+  }
+  
+  // SHA256 加密后是64位十六进制字符串
+  return /^[a-f0-9]{64}$/i.test(password);
+};
+
+/**
+ * 安全的密码加密函数,避免重复加密
+ * @param {string} password 密码
+ * @returns {string} 加密后的密码
+ */
+export const safeHashPassword = (password) => {
+  if (!password) {
+    throw new Error('密码不能为空');
+  }
+  
+  // 如果已经是加密的密码,直接返回
+  if (isEncryptedPassword(password)) {
+    return password;
+  }
+  
+  // 否则进行加密
+  return hashPassword(password);
+};