推荐系统
Change-Id: I49b9205568f1ccf88b32b08511aff8b0bea8d1bd
diff --git a/rhj/backend/app/blueprints/__pycache__/__init__.cpython-312.pyc b/rhj/backend/app/blueprints/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..1388273
--- /dev/null
+++ b/rhj/backend/app/blueprints/__pycache__/__init__.cpython-312.pyc
Binary files differ
diff --git a/rhj/backend/app/blueprints/__pycache__/recommend.cpython-312.pyc b/rhj/backend/app/blueprints/__pycache__/recommend.cpython-312.pyc
new file mode 100644
index 0000000..29c786d
--- /dev/null
+++ b/rhj/backend/app/blueprints/__pycache__/recommend.cpython-312.pyc
Binary files differ
diff --git a/rhj/backend/app/blueprints/recommend.py b/rhj/backend/app/blueprints/recommend.py
new file mode 100644
index 0000000..97b8908
--- /dev/null
+++ b/rhj/backend/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