fix(security): 代码审查安全加固 — 三批次修复(6H/13M/12L)
第一批(快速修复): - H6: main.py --debug 默认值 True→False,防止 Werkzeug RCE - M2+M3: /search 增加 validate_query + top_k 范围限制(1-50) - M4: context_count 范围限制(0-10) + 异常捕获 - L3: assert → raise RuntimeError(生产环境 API Key 检查) - H1: SSE 错误事件移除 traceback 字段 第二批(安全加固): - H2+H3: 文档接口路径遍历 realpath 校验 + 文件类型/大小限制 - H4+H5: 批量上传文件大小检查 - M6: LIKE 查询通配符转义 - M1: 37 处 str(e) 异常信息统一脱敏(6 文件) - M5: CORS 生产环境限制来源 - M7: SESSION_MANAGER None 保护(503) - M11: subprocess 参数注入防护(白名单 + -- 分隔符) 第三批(架构改进): - M8+M9: 提取 JSON 解析共享工具(extract_json_object/list) - M10: Prompt 注入检测防御(prompt_guard.py) - M12: 解析器文件大小限制(Excel 50MB/TXT 20MB/PDF 100MB) - M13: 全局单例竞态条件双重检查锁定(engine/bm25/intent_analyzer)
This commit is contained in:
@@ -67,7 +67,17 @@ def create_app() -> 'Flask':
|
||||
static_folder = os.path.join(PROJECT_ROOT, 'chat-ui')
|
||||
|
||||
app = Flask(__name__, static_folder=static_folder, static_url_path='')
|
||||
CORS(app)
|
||||
|
||||
# CORS 配置:生产环境限制来源,开发环境允许全部
|
||||
if IS_PROD:
|
||||
cors_origins = os.environ.get('CORS_ORIGINS', '').split(',') if os.environ.get('CORS_ORIGINS') else []
|
||||
if cors_origins:
|
||||
CORS(app, origins=cors_origins)
|
||||
else:
|
||||
CORS(app) # 未配置时仍允许全部,但记录警告
|
||||
logger.warning("生产环境未配置 CORS_ORIGINS,CORS 允许所有来源")
|
||||
else:
|
||||
CORS(app)
|
||||
|
||||
# ==================== Repository 依赖注入 ====================
|
||||
|
||||
@@ -227,14 +237,14 @@ def _validate_production_config() -> None:
|
||||
- DASHSCOPE_API_KEY: 大模型调用必需
|
||||
|
||||
Raises:
|
||||
AssertionError: 缺少必需的配置项
|
||||
RuntimeError: 缺少必需的配置项
|
||||
"""
|
||||
import os
|
||||
from config import DASHSCOPE_API_KEY
|
||||
# 检查环境变量或配置文件中的 API Key
|
||||
has_key = os.getenv("DASHSCOPE_API_KEY") or os.environ.get("DASHSCOPE_API_KEY") or DASHSCOPE_API_KEY
|
||||
assert has_key and has_key != "", \
|
||||
"Missing DASHSCOPE_API_KEY in production environment"
|
||||
if not has_key or has_key == "":
|
||||
raise RuntimeError("Missing DASHSCOPE_API_KEY in production environment")
|
||||
logger.info("Configuration validated")
|
||||
|
||||
|
||||
|
||||
@@ -97,7 +97,8 @@ def get_audit_logs():
|
||||
return jsonify({"logs": logs, "total": total})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e), "logs": [], "total": 0}), 500
|
||||
logger.error(f"审计查询异常: {e}")
|
||||
return jsonify({"error": "查询失败", "logs": [], "total": 0}), 500
|
||||
|
||||
|
||||
def log_audit_event(user_id: str, username: str, action: str,
|
||||
|
||||
@@ -41,6 +41,7 @@ from auth.gateway import require_gateway_auth
|
||||
from auth.security import validate_query, filter_response
|
||||
from config import RAG_CHAT_MODEL
|
||||
from core.llm_utils import call_llm, call_llm_stream
|
||||
from core.prompt_guard import detect_injection, sanitize_user_input
|
||||
|
||||
|
||||
def _get_vlm_cache(image_path: str) -> Optional[str]:
|
||||
@@ -1953,11 +1954,11 @@ def rag():
|
||||
yield f"data: {json.dumps(finish_event, ensure_ascii=False)}\n\n"
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
import logging as _logging
|
||||
_logging.getLogger(__name__).error(f"[SSE] RAG 流异常: {e}", exc_info=True)
|
||||
error_event = {
|
||||
"type": "error",
|
||||
"message": str(e),
|
||||
"traceback": traceback.format_exc()
|
||||
"message": "服务内部错误,请稍后重试"
|
||||
}
|
||||
yield f"data: {json.dumps(error_event, ensure_ascii=False)}\n\n"
|
||||
|
||||
@@ -1986,12 +1987,27 @@ def search():
|
||||
"""
|
||||
data = request.json or {}
|
||||
query = data.get('query', '')
|
||||
query = sanitize_user_input(query)
|
||||
injection_matches = detect_injection(query)
|
||||
if injection_matches:
|
||||
logger.warning(f"[Chat] 检测到可疑注入: {injection_matches}")
|
||||
top_k = data.get('top_k', 5)
|
||||
collections = data.get('collections') # 后端传入的知识库列表
|
||||
|
||||
if not query:
|
||||
return jsonify({'error': 'query is required'}), 400
|
||||
|
||||
# 输入安全校验(注入检测、违禁词、长度限制)
|
||||
is_valid, reason = validate_query(query)
|
||||
if not is_valid:
|
||||
return jsonify({'error': reason}), 400
|
||||
|
||||
# top_k 范围校验
|
||||
try:
|
||||
top_k = max(1, min(int(top_k), 50))
|
||||
except (ValueError, TypeError):
|
||||
top_k = 5
|
||||
|
||||
# 如果没有指定 collections,使用默认的公开库
|
||||
if not collections:
|
||||
collections = ['public_kb']
|
||||
|
||||
@@ -96,6 +96,24 @@ def safe_filename(filename: str) -> str:
|
||||
|
||||
return safe_name
|
||||
|
||||
|
||||
def _validate_doc_path(doc_path: str, documents_path: str) -> str:
|
||||
"""
|
||||
校验文档路径是否在允许目录内,防止路径遍历攻击。
|
||||
|
||||
Returns:
|
||||
解析后的安全绝对路径
|
||||
Raises:
|
||||
ValueError: 路径不合法
|
||||
"""
|
||||
filepath = os.path.join(documents_path, doc_path)
|
||||
real_path = os.path.realpath(filepath)
|
||||
real_base = os.path.realpath(documents_path)
|
||||
if not real_path.startswith(real_base + os.sep) and real_path != real_base:
|
||||
raise ValueError("非法路径")
|
||||
return real_path
|
||||
|
||||
|
||||
# 延迟初始化缓存
|
||||
_kb_manager = None
|
||||
_kb_checked = False
|
||||
@@ -160,6 +178,10 @@ def serve_document_file(doc_path: str) -> Tuple[Any, int]:
|
||||
from flask import send_from_directory
|
||||
|
||||
filepath = os.path.join(DOCUMENTS_PATH, doc_path)
|
||||
try:
|
||||
filepath = _validate_doc_path(doc_path, DOCUMENTS_PATH)
|
||||
except ValueError:
|
||||
return jsonify({"error": "非法路径"}), 403
|
||||
if not os.path.exists(filepath):
|
||||
return jsonify({"error": "文件不存在"}), 404
|
||||
|
||||
@@ -329,7 +351,8 @@ def upload_document() -> Tuple[Any, int]:
|
||||
sync_service.process_change(change)
|
||||
sync_status = "已保存并添加到向量库"
|
||||
except Exception as e:
|
||||
sync_status = f"已保存,向量化失败: {str(e)}"
|
||||
logger.warning(f"向量化失败: {e}")
|
||||
sync_status = "已保存,向量化失败"
|
||||
|
||||
return success_response(
|
||||
data={
|
||||
@@ -404,6 +427,18 @@ def batch_upload_documents() -> Tuple[Any, int]:
|
||||
})
|
||||
continue
|
||||
|
||||
# 文件大小校验
|
||||
file.seek(0, 2)
|
||||
file_size = file.tell()
|
||||
file.seek(0)
|
||||
if file_size > MAX_FILE_SIZE:
|
||||
results.append({
|
||||
"filename": file.filename,
|
||||
"status": "error",
|
||||
"message": f"文件过大(最大 {MAX_FILE_SIZE // 1024 // 1024}MB)"
|
||||
})
|
||||
continue
|
||||
|
||||
try:
|
||||
original_filename = file.filename
|
||||
ext = os.path.splitext(original_filename)[1].lower()
|
||||
@@ -464,10 +499,11 @@ def batch_upload_documents() -> Tuple[Any, int]:
|
||||
"replaced": replaced
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"上传处理异常: {e}")
|
||||
results.append({
|
||||
"filename": file.filename,
|
||||
"status": "error",
|
||||
"message": str(e)
|
||||
"message": "上传处理失败"
|
||||
})
|
||||
|
||||
return success_response(
|
||||
@@ -644,12 +680,27 @@ def update_document(doc_path: str) -> Tuple[Any, int]:
|
||||
if file.filename == '':
|
||||
return jsonify({"error": "没有选择文件"}), 400
|
||||
|
||||
# 文件类型校验
|
||||
ext = os.path.splitext(file.filename)[1].lower()
|
||||
if ext not in ALLOWED_EXTENSIONS:
|
||||
return jsonify({"error": f"不支持的文件类型: {ext}"}), 400
|
||||
|
||||
# 文件大小校验
|
||||
file.seek(0, 2) # 跳到文件末尾获取大小
|
||||
file_size = file.tell()
|
||||
file.seek(0) # 回到文件开头
|
||||
if file_size > MAX_FILE_SIZE:
|
||||
return jsonify({"error": f"文件过大(最大 {MAX_FILE_SIZE // 1024 // 1024}MB)"}), 400
|
||||
|
||||
# 解析路径
|
||||
parts = doc_path.split('/')
|
||||
if len(parts) < 2:
|
||||
return jsonify({"error": "无效的文档路径"}), 400
|
||||
|
||||
filepath = os.path.join(DOCUMENTS_PATH, doc_path)
|
||||
try:
|
||||
filepath = _validate_doc_path(doc_path, DOCUMENTS_PATH)
|
||||
except ValueError:
|
||||
return jsonify({"error": "非法路径"}), 403
|
||||
if not os.path.exists(filepath):
|
||||
return jsonify({"error": "文件不存在"}), 404
|
||||
|
||||
@@ -711,7 +762,10 @@ def delete_document(doc_path: str) -> Tuple[Any, int]:
|
||||
# 目录名即向量库名
|
||||
collection = subdir
|
||||
|
||||
filepath = os.path.join(DOCUMENTS_PATH, doc_path)
|
||||
try:
|
||||
filepath = _validate_doc_path(doc_path, DOCUMENTS_PATH)
|
||||
except ValueError:
|
||||
return jsonify({"error": "非法路径"}), 403
|
||||
if not os.path.exists(filepath):
|
||||
return jsonify({"error": "文件不存在"}), 404
|
||||
|
||||
@@ -730,7 +784,8 @@ def delete_document(doc_path: str) -> Tuple[Any, int]:
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"error": f"删除失败: {str(e)}"}), 500
|
||||
logger.error(f"删除文档异常: {e}")
|
||||
return jsonify({"error": "删除失败,请稍后重试"}), 500
|
||||
|
||||
|
||||
@document_bp.route('/documents/<path:doc_path>/chunks', methods=['GET'])
|
||||
@@ -817,7 +872,10 @@ def preview_document(doc_path: str) -> Tuple[Any, int]:
|
||||
|
||||
# 查询参数
|
||||
chunk_index_str = request.args.get('chunk_index')
|
||||
context_count = int(request.args.get('context', 2))
|
||||
try:
|
||||
context_count = max(0, min(int(request.args.get('context', 2)), 10))
|
||||
except (ValueError, TypeError):
|
||||
context_count = 2
|
||||
|
||||
# 获取所有切片
|
||||
all_chunks = kb_manager.get_document_chunks(collection, filename)
|
||||
@@ -1082,5 +1140,5 @@ def delete_chunks_by_source() -> Tuple[Any, int]:
|
||||
"message": f"已删除 {deleted_count} 个切片"
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"批量删除切片失败: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
logger.error(f"操作异常: {e}")
|
||||
return jsonify({"error": "操作失败,请稍后重试"}), 500
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
|
||||
from flask import Blueprint, request, jsonify
|
||||
from auth.gateway import require_gateway_auth, require_role
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
feedback_bp = Blueprint('feedback', __name__)
|
||||
|
||||
@@ -95,7 +98,8 @@ def submit_feedback():
|
||||
"suggestion_id": result.get('suggestion_id')
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
logger.error(f"反馈操作异常: {e}")
|
||||
return jsonify({"error": "操作失败,请稍后重试"}), 500
|
||||
|
||||
|
||||
@feedback_bp.route('/feedback/stats', methods=['GET'])
|
||||
@@ -113,7 +117,8 @@ def get_feedback_stats():
|
||||
"stats": stats
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
logger.error(f"反馈操作异常: {e}")
|
||||
return jsonify({"error": "操作失败,请稍后重试"}), 500
|
||||
|
||||
|
||||
@feedback_bp.route('/feedback/list', methods=['GET'])
|
||||
@@ -141,7 +146,8 @@ def get_feedback_list():
|
||||
"total": len(feedbacks)
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
logger.error(f"反馈操作异常: {e}")
|
||||
return jsonify({"error": "操作失败,请稍后重试"}), 500
|
||||
|
||||
|
||||
@feedback_bp.route('/reports/weekly', methods=['GET'])
|
||||
@@ -156,7 +162,8 @@ def get_weekly_report():
|
||||
"report": report.to_dict()
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
logger.error(f"反馈操作异常: {e}")
|
||||
return jsonify({"error": "操作失败,请稍后重试"}), 500
|
||||
|
||||
|
||||
@feedback_bp.route('/reports/monthly', methods=['GET'])
|
||||
@@ -171,7 +178,8 @@ def get_monthly_report():
|
||||
"report": report.to_dict()
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
logger.error(f"反馈操作异常: {e}")
|
||||
return jsonify({"error": "操作失败,请稍后重试"}), 500
|
||||
|
||||
|
||||
@feedback_bp.route('/faq', methods=['GET'])
|
||||
@@ -190,7 +198,8 @@ def get_faq_list():
|
||||
"total": len(faqs)
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
logger.error(f"反馈操作异常: {e}")
|
||||
return jsonify({"error": "操作失败,请稍后重试"}), 500
|
||||
|
||||
|
||||
@feedback_bp.route('/faq', methods=['POST'])
|
||||
@@ -233,7 +242,8 @@ def create_faq():
|
||||
"message": "FAQ已创建,请通过 /faq/<id>/approve 接口确认后生效"
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
logger.error(f"反馈操作异常: {e}")
|
||||
return jsonify({"error": "操作失败,请稍后重试"}), 500
|
||||
|
||||
|
||||
@feedback_bp.route('/faq/<int:faq_id>/approve', methods=['POST'])
|
||||
@@ -276,7 +286,8 @@ def approve_faq(faq_id):
|
||||
"message": "FAQ已批准并同步到知识库"
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
logger.error(f"反馈操作异常: {e}")
|
||||
return jsonify({"error": "操作失败,请稍后重试"}), 500
|
||||
|
||||
|
||||
@feedback_bp.route('/faq/<int:faq_id>', methods=['PUT'])
|
||||
@@ -326,7 +337,8 @@ def update_faq(faq_id):
|
||||
"sync_status": sync_status
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
logger.error(f"反馈操作异常: {e}")
|
||||
return jsonify({"error": "操作失败,请稍后重试"}), 500
|
||||
|
||||
|
||||
@feedback_bp.route('/faq/<int:faq_id>', methods=['DELETE'])
|
||||
@@ -358,7 +370,8 @@ def delete_faq(faq_id):
|
||||
"message": "FAQ删除成功" if deleted else "FAQ不存在"
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
logger.error(f"反馈操作异常: {e}")
|
||||
return jsonify({"error": "操作失败,请稍后重试"}), 500
|
||||
|
||||
|
||||
@feedback_bp.route('/faq/suggestions', methods=['GET'])
|
||||
@@ -378,7 +391,8 @@ def get_faq_suggestions():
|
||||
"total": len(suggestions)
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
logger.error(f"反馈操作异常: {e}")
|
||||
return jsonify({"error": "操作失败,请稍后重试"}), 500
|
||||
|
||||
|
||||
@feedback_bp.route('/faq/suggestions/<int:suggestion_id>/approve', methods=['POST'])
|
||||
@@ -413,7 +427,8 @@ def approve_faq_suggestion(suggestion_id):
|
||||
else:
|
||||
return jsonify({"error": result.get('error', '批准失败')}), 400
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
logger.error(f"反馈操作异常: {e}")
|
||||
return jsonify({"error": "操作失败,请稍后重试"}), 500
|
||||
|
||||
|
||||
@feedback_bp.route('/faq/suggestions/<int:suggestion_id>/reject', methods=['POST'])
|
||||
@@ -429,7 +444,8 @@ def reject_faq_suggestion(suggestion_id):
|
||||
"message": "FAQ建议已拒绝" if rejected else "建议不存在"
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
logger.error(f"反馈操作异常: {e}")
|
||||
return jsonify({"error": "操作失败,请稍后重试"}), 500
|
||||
|
||||
|
||||
# ==================== Bad Case 分析接口 ====================
|
||||
@@ -472,7 +488,8 @@ def get_bad_cases():
|
||||
]
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
logger.error(f"反馈操作异常: {e}")
|
||||
return jsonify({"error": "操作失败,请稍后重试"}), 500
|
||||
|
||||
|
||||
@feedback_bp.route('/feedback/blacklist', methods=['GET'])
|
||||
@@ -497,4 +514,5 @@ def get_chunk_blacklist():
|
||||
"usage": "在检索时过滤这些来源以提升回答质量"
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
logger.error(f"反馈操作异常: {e}")
|
||||
return jsonify({"error": "操作失败,请稍后重试"}), 500
|
||||
|
||||
@@ -9,8 +9,11 @@
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
from flask import Blueprint, send_file, jsonify, current_app
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
image_bp = Blueprint('images', __name__)
|
||||
|
||||
|
||||
@@ -70,7 +73,8 @@ def get_image(image_id: str):
|
||||
|
||||
return send_file(image_path, mimetype=mimetype)
|
||||
except Exception as e:
|
||||
return jsonify({"error": f"读取图片失败: {str(e)}"}), 500
|
||||
logger.error(f"读取图片异常: {e}")
|
||||
return jsonify({"error": "读取图片失败"}), 500
|
||||
|
||||
return jsonify({"error": "图片不存在", "image_id": image_id}), 404
|
||||
|
||||
@@ -122,7 +126,8 @@ def get_image_info(image_id: str):
|
||||
"url": f"/images/{image_id}"
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": f"读取图片信息失败: {str(e)}"}), 500
|
||||
logger.error(f"读取图片信息异常: {e}")
|
||||
return jsonify({"error": "读取图片信息失败"}), 500
|
||||
|
||||
return jsonify({"error": "图片不存在", "image_id": image_id}), 404
|
||||
|
||||
@@ -179,7 +184,8 @@ def list_images():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"error": f"列出图片失败: {str(e)}"}), 500
|
||||
logger.error(f"列出图片异常: {e}")
|
||||
return jsonify({"error": "列出图片失败"}), 500
|
||||
|
||||
|
||||
@image_bp.route('/images/stats', methods=['GET'])
|
||||
@@ -222,4 +228,5 @@ def image_stats():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"error": f"获取统计信息失败: {str(e)}"}), 500
|
||||
logger.error(f"获取统计信息异常: {e}")
|
||||
return jsonify({"error": "获取统计信息失败"}), 500
|
||||
|
||||
@@ -391,10 +391,11 @@ def sync_documents() -> Tuple[Any, int]:
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"知识库操作异常: {e}")
|
||||
results.append({
|
||||
"collection": "all",
|
||||
"status": "error",
|
||||
"message": str(e)
|
||||
"message": "操作失败"
|
||||
})
|
||||
else:
|
||||
# 没有 sync_service,返回提示
|
||||
@@ -462,7 +463,8 @@ def debug_scan() -> Tuple[Any, int]:
|
||||
result["scanned_count"] = len(scanned)
|
||||
result["scanned_ids"] = list(scanned.keys())[:10]
|
||||
except Exception as e:
|
||||
result["scan_error"] = str(e)
|
||||
logger.error(f"扫描异常: {e}")
|
||||
result["scan_error"] = "扫描失败"
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
@@ -499,9 +501,11 @@ def reindex_collection(kb_name: str) -> Tuple[Any, int]:
|
||||
from data.db import get_connection
|
||||
with get_connection("knowledge") as conn:
|
||||
cursor = conn.cursor()
|
||||
# 转义 LIKE 通配符,防止 kb_name 中的 % 或 _ 导致非预期匹配
|
||||
escaped_kb = kb_name.replace('%', '\\%').replace('_', '\\_')
|
||||
# 删除以 "{kb_name}/" 或 "{kb_name}\" 开头的文档哈希(兼容 Windows 和 Linux)
|
||||
cursor.execute("DELETE FROM document_hashes WHERE document_id LIKE ? OR document_id LIKE ?",
|
||||
(f"{kb_name}/%", f"{kb_name}\\%"))
|
||||
cursor.execute("DELETE FROM document_hashes WHERE document_id LIKE ? ESCAPE '\\' OR document_id LIKE ? ESCAPE '\\'",
|
||||
(f"{escaped_kb}/%", f"{escaped_kb}\\%"))
|
||||
deleted = cursor.rowcount
|
||||
logger.info(f"已清除 {deleted} 条哈希记录: {kb_name}")
|
||||
except Exception as e:
|
||||
@@ -520,7 +524,8 @@ def reindex_collection(kb_name: str) -> Tuple[Any, int]:
|
||||
"errors": result.errors
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
logger.error(f"reindex 异常: {e}")
|
||||
return jsonify({"error": "操作失败,请稍后重试"}), 500
|
||||
else:
|
||||
return jsonify({"error": "同步服务不可用"}), 503
|
||||
|
||||
@@ -633,7 +638,8 @@ def deprecate_document(kb_name: str, filename: str) -> Tuple[Any, int]:
|
||||
)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
logger.error(f"操作异常: {e}")
|
||||
return jsonify({"success": False, "error": "操作失败,请稍后重试"}), 500
|
||||
|
||||
|
||||
@kb_bp.route('/collections/<kb_name>/documents/<path:filename>/restore', methods=['POST'])
|
||||
@@ -664,7 +670,8 @@ def restore_document(kb_name: str, filename: str) -> Tuple[Any, int]:
|
||||
result = kb_manager.restore_document(kb_name, filename)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
logger.error(f"操作异常: {e}")
|
||||
return jsonify({"success": False, "error": "操作失败,请稍后重试"}), 500
|
||||
|
||||
|
||||
@kb_bp.route('/collections/<kb_name>/documents/<path:filename>/versions', methods=['GET'])
|
||||
@@ -720,7 +727,8 @@ def get_document_versions(kb_name: str, filename: str) -> Tuple[Any, int]:
|
||||
"total": len(versions_data)
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
logger.error(f"操作异常: {e}")
|
||||
return jsonify({"success": False, "error": "操作失败,请稍后重试"}), 500
|
||||
|
||||
|
||||
@kb_bp.route('/collections/<kb_name>/update-image-descriptions', methods=['POST'])
|
||||
@@ -754,7 +762,8 @@ def update_image_descriptions(kb_name: str) -> Tuple[Any, int]:
|
||||
result = kb_manager.update_image_descriptions(kb_name)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
logger.error(f"操作异常: {e}")
|
||||
return jsonify({"success": False, "error": "操作失败,请稍后重试"}), 500
|
||||
|
||||
|
||||
@kb_bp.route('/collections/sync-vlm-cache', methods=['POST'])
|
||||
|
||||
@@ -33,6 +33,8 @@ def get_sessions():
|
||||
}
|
||||
"""
|
||||
session_manager = current_app.config['SESSION_MANAGER']
|
||||
if session_manager is None:
|
||||
return jsonify({"error": "会话服务不可用"}), 503
|
||||
user_id = request.current_user["user_id"]
|
||||
|
||||
sessions = session_manager.get_user_sessions(user_id, limit=20)
|
||||
@@ -62,6 +64,8 @@ def get_history(session_id):
|
||||
}
|
||||
"""
|
||||
session_manager = current_app.config['SESSION_MANAGER']
|
||||
if session_manager is None:
|
||||
return jsonify({"error": "会话服务不可用"}), 503
|
||||
user_id = request.current_user["user_id"]
|
||||
|
||||
# 验证会话归属
|
||||
@@ -81,6 +85,8 @@ def get_history(session_id):
|
||||
def delete_session(session_id):
|
||||
"""删除会话"""
|
||||
session_manager = current_app.config['SESSION_MANAGER']
|
||||
if session_manager is None:
|
||||
return jsonify({"error": "会话服务不可用"}), 503
|
||||
user_id = request.current_user["user_id"]
|
||||
|
||||
# 验证会话归属
|
||||
@@ -100,6 +106,8 @@ def delete_session(session_id):
|
||||
def clear_history(session_id):
|
||||
"""清空会话历史(保留会话)"""
|
||||
session_manager = current_app.config['SESSION_MANAGER']
|
||||
if session_manager is None:
|
||||
return jsonify({"error": "会话服务不可用"}), 503
|
||||
user_id = request.current_user["user_id"]
|
||||
|
||||
# 验证会话归属
|
||||
|
||||
@@ -112,10 +112,11 @@ def trigger_sync() -> Tuple[Any, int]:
|
||||
message="同步完成"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"同步操作异常: {e}")
|
||||
return error_response(
|
||||
error="SYNC_ERROR",
|
||||
error_code=SYNC_ERROR,
|
||||
message=str(e),
|
||||
message="操作失败",
|
||||
http_status=500
|
||||
)
|
||||
|
||||
@@ -164,11 +165,12 @@ def get_sync_status() -> Tuple[Any, int]:
|
||||
|
||||
return jsonify(status)
|
||||
except Exception as e:
|
||||
logger.error(f"状态查询异常: {e}")
|
||||
return jsonify({
|
||||
"status": "failed",
|
||||
"status_code": INTERNAL_ERROR,
|
||||
"enabled": True,
|
||||
"error": str(e)
|
||||
"error": "操作失败"
|
||||
})
|
||||
|
||||
|
||||
@@ -196,10 +198,11 @@ def get_sync_history() -> Tuple[Any, int]:
|
||||
history = service.get_sync_history(limit=limit) if hasattr(service, 'get_sync_history') else []
|
||||
return jsonify({"history": history})
|
||||
except Exception as e:
|
||||
logger.error(f"同步操作异常: {e}")
|
||||
return error_response(
|
||||
error="SYNC_ERROR",
|
||||
error_code=SYNC_ERROR,
|
||||
message=str(e),
|
||||
message="操作失败",
|
||||
http_status=500
|
||||
)
|
||||
|
||||
@@ -230,10 +233,11 @@ def get_change_logs() -> Tuple[Any, int]:
|
||||
changes = service.get_change_logs(limit=limit, collection=collection) if hasattr(service, 'get_change_logs') else []
|
||||
return jsonify({"changes": changes})
|
||||
except Exception as e:
|
||||
logger.error(f"同步操作异常: {e}")
|
||||
return error_response(
|
||||
error="SYNC_ERROR",
|
||||
error_code=SYNC_ERROR,
|
||||
message=str(e),
|
||||
message="操作失败",
|
||||
http_status=500
|
||||
)
|
||||
|
||||
@@ -275,10 +279,11 @@ def start_sync_monitor() -> Tuple[Any, int]:
|
||||
else:
|
||||
return jsonify({"status": "success", "message": "文件监控功能不可用"})
|
||||
except Exception as e:
|
||||
logger.error(f"同步操作异常: {e}")
|
||||
return error_response(
|
||||
error="SYNC_ERROR",
|
||||
error_code=SYNC_ERROR,
|
||||
message=str(e),
|
||||
message="操作失败",
|
||||
http_status=500
|
||||
)
|
||||
|
||||
@@ -303,9 +308,10 @@ def stop_sync_monitor() -> Tuple[Any, int]:
|
||||
service.stop()
|
||||
return jsonify({"status": "success", "status_code": SYNC_SUCCESS, "message": "文件监控已停止"})
|
||||
except Exception as e:
|
||||
logger.error(f"同步操作异常: {e}")
|
||||
return error_response(
|
||||
error="SYNC_ERROR",
|
||||
error_code=SYNC_ERROR,
|
||||
message=str(e),
|
||||
message="操作失败",
|
||||
http_status=500
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user