| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- import re
- import time
- import hashlib
- from typing import Dict, List, Optional, Tuple
- from functools import wraps
- from flask import request, jsonify
- from http import HTTPStatus
- from datetime import datetime, timezone, timedelta
- def get_beijing_time() -> datetime:
- """
- 获取北京时间(UTC+8)
-
- Returns:
- 北京时间的datetime对象
- """
- beijing_tz = timezone(timedelta(hours=8))
- return datetime.now(beijing_tz)
- def sanitize_input(text: str, max_length: int = 20000) -> Tuple[str, Optional[str]]:
- """
- 清理和验证用户输入
-
- Args:
- text: 输入文本
- max_length: 最大长度限制
-
- Returns:
- (清理后的文本, 错误信息)
- """
- if not text or not text.strip():
- return "", "消息不能为空"
-
- # 移除首尾空白
- text = text.strip()
-
- # 检查长度
- if len(text) > max_length:
- return "", f"消息长度不能超过{max_length}字符"
-
- # 移除潜在的恶意脚本标签
- text = re.sub(r'<script[^>]*>.*?</script>', '', text, flags=re.IGNORECASE | re.DOTALL)
- text = re.sub(r'<iframe[^>]*>.*?</iframe>', '', text, flags=re.IGNORECASE | re.DOTALL)
-
- # 限制连续换行
- text = re.sub(r'\n{3,}', '\n\n', text)
-
- return text, None
- def validate_user_id(user_id: str) -> Tuple[str, Optional[str]]:
- """
- 验证用户ID
-
- Args:
- user_id: 用户ID
-
- Returns:
- (验证后的用户ID, 错误信息)
- """
- if not user_id:
- return "", "用户ID不能为空"
-
- # 检查用户ID格式(UUID格式)
- uuid_pattern = re.compile(
- r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$',
- re.IGNORECASE
- )
-
- if not uuid_pattern.match(user_id):
- return "", "用户ID格式无效"
-
- return user_id, None
- class RateLimiter:
- """简单的内存速率限制器"""
-
- def __init__(self, max_requests: int, window_seconds: int):
- self.max_requests = max_requests
- self.window_seconds = window_seconds
- self.requests: Dict[str, List[float]] = {}
-
- def is_allowed(self, identifier: str) -> bool:
- """
- 检查请求是否被允许
-
- Args:
- identifier: 请求标识符(如IP地址或用户ID)
-
- Returns:
- 是否允许请求
- """
- now = time.time()
-
- # 清理过期的请求记录
- if identifier in self.requests:
- self.requests[identifier] = [
- req_time for req_time in self.requests[identifier]
- if now - req_time < self.window_seconds
- ]
- else:
- self.requests[identifier] = []
-
- # 检查是否超过限制
- if len(self.requests[identifier]) >= self.max_requests:
- return False
-
- # 记录新请求
- self.requests[identifier].append(now)
- return True
-
- def get_remaining(self, identifier: str) -> int:
- """获取剩余请求次数"""
- now = time.time()
- if identifier in self.requests:
- valid_requests = [
- req_time for req_time in self.requests[identifier]
- if now - req_time < self.window_seconds
- ]
- return max(0, self.max_requests - len(valid_requests))
- return self.max_requests
- def rate_limit(max_requests: int, window_seconds: int, identifier_func=None):
- """
- 速率限制装饰器
-
- Args:
- max_requests: 最大请求数
- window_seconds: 时间窗口(秒)
- identifier_func: 获取标识符的函数,默认为IP地址
- """
- limiter = RateLimiter(max_requests, window_seconds)
-
- def decorator(f):
- @wraps(f)
- def decorated_function(*args, **kwargs):
- # 获取请求标识符
- if identifier_func:
- identifier = identifier_func()
- else:
- identifier = request.remote_addr or 'unknown'
-
- if not limiter.is_allowed(identifier):
- remaining_time = window_seconds
- return jsonify({
- 'success': False,
- 'error': f'请求过于频繁,请等待{remaining_time}秒后重试',
- 'rate_limit': {
- 'max_requests': max_requests,
- 'window_seconds': window_seconds,
- 'remaining': 0
- }
- }), HTTPStatus.TOO_MANY_REQUESTS
-
- # 添加速率限制信息到响应头
- response = f(*args, **kwargs)
- if hasattr(response, 'headers'):
- response.headers['X-RateLimit-Limit'] = str(max_requests)
- response.headers['X-RateLimit-Remaining'] = str(limiter.get_remaining(identifier))
- response.headers['X-RateLimit-Reset'] = str(int(time.time() + window_seconds))
-
- return response
- return decorated_function
- return decorator
- def truncate_conversation_history(history: List[Dict[str, str]], max_length: int) -> List[Dict[str, str]]:
- """
- 截断对话历史,保持最新的消息
-
- Args:
- history: 对话历史列表
- max_length: 最大历史长度
-
- Returns:
- 截断后的对话历史
- """
- if len(history) <= max_length:
- return history
-
- # 保留最新的消息,但确保至少保留用户和助手的各一条消息
- if max_length >= 2:
- return history[-max_length:]
- else:
- return history[-1:] if history else []
- def generate_session_id(user_id: int, conversation_id: int = None) -> str:
- """
- 生成基于时间和用户ID的session_id
-
- Args:
- user_id: 用户ID
- conversation_id: 对话ID(可选)
-
- Returns:
- 生成的session_id字符串(16位)
- """
- # 获取当前时间戳(毫秒)
- timestamp = int(time.time() * 1000)
-
- # 构建基础字符串
- base_string = f"{user_id}_{timestamp}"
- if conversation_id:
- base_string += f"_{conversation_id}"
-
- # 使用MD5生成哈希值,然后取前16位
- hash_object = hashlib.md5(base_string.encode())
- session_id = hash_object.hexdigest()[:16] # 只取前16位
-
- return session_id
- def log_request_info(message: str, user_id: str, request_method: str = None):
- """记录请求信息的辅助函数"""
- if not request_method:
- request_method = request.method
-
- from app import logger
- logger.info(
- f"收到{request_method}请求: "
- f"消息长度={len(message)}字符, "
- f"user_id={user_id}, "
- f"IP={request.remote_addr or 'unknown'}"
- )
|