
    miK                       U d Z ddlmZ ddlZddlmZ ddlmZmZ ddl	m
Z
 ddlmZ ddlmZ dd	lmZ dd
lmZ  ee          Zdej        ej        fdej        ej        fdgZded<   ej        ej        fZddZddZ G d de          Z dS )u  Fixed-window rate-limiting middleware backed by Redis.

Strategy
--------
For each (path_prefix, identifier) pair, we maintain a Redis key with a
fixed-window counter (按时间窗口分桶计数).  The identifier is the JWT
``sub`` claim when available, otherwise the client IP address.

Limits (requests / window_seconds):
    POST /api/ai/v1/research   →  10 / 60   (LLM-heavy endpoint)
    GET  /api/ai/v1/search     →  60 / 60
    POST /api/ai/v1/ingest     → 200 / 60   (machine-to-machine webhook)
    *  (default)               → 200 / 60

Configuration can be overridden via environment variables:
    RATE_LIMIT_RESEARCH   = "10/60"
    RATE_LIMIT_SEARCH     = "60/60"
    RATE_LIMIT_DEFAULT    = "200/60"
    RATE_LIMIT_ENABLED    = "true"

基于 Redis 固定窗口的请求限流中间件。
按 (路径前缀, 用户标识) 维度计数，超过阈值返回 429。
采用 fail-open 策略：Redis 不可用时放行请求，避免因缓存故障阻塞业务。
    )annotationsN)Callable)RequestResponse)JSONResponse)BaseHTTPMiddleware)ASGIApp)settings)
get_loggerz/api/ai/v1/researchz/api/ai/v1/search)z/api/ai/v1/ingest   <   zlist[tuple[str, int, int]]_LIMITSpathstrreturntuple[int, int]c                b    t           D ]!\  }}}|                     |          r||fc S "t          S N)r   
startswith_DEFAULT_LIMIT)r   prefixmax_reqwindows       3D:\work\zm-rag\backend\app\middleware\rate_limit.py
_get_limitr   5   sG    #* # #??6"" 	#F?"""	#    requestr   c                v   | j                             dd          }|                    d          r|dd         }	 ddl}ddl}|                    d          }t          |          dk    r\|d	         d
t          |d	                    dz  z  z   }|                    |                    |                    }d|v rd|d          S n# t          $ r Y nw xY w| j                             dd          }|r0d|                    d          d         
                                 S | j        }	d|	r|	j        nd S )z4Extract per-client identifier: JWT sub or client IP.Authorization zBearer    Nr   .      =   subzuser:zX-Forwarded-Forzip:,unknown)headersgetr   base64jsonsplitlenloadsurlsafe_b64decode	Exceptionstripclienthost)
r   auth_headertokenr,   _jsonpartspayload_b64payloadforwarded_forr4   s
             r   _extract_identifierr=   <   si   /%%or::Ki(( ABB	((((((((KK$$E5zzQ#AhU1X0B)CC++f&>&>{&K&KLLG##375>333 	 	 	D	 O''(92>>M ;:]((--a06688:::^F75I777s   BC	 	
CCc                  ,     e Zd ZdZd fdZddZ xZS )RateLimitMiddlewareu(  Fixed-window rate limiting via Redis atomic INCR + EXPIRE.

    Skips rate limiting if Redis is unavailable (fail-open).

    固定窗口限流中间件，使用 Redis INCR + EXPIRE 原子操作实现计数。
    仅对 /api/ai/ 路径生效，Redis 故障时自动放行（fail-open）。
    appr	   r   Nonec                J    t                                          |           d S r   )super__init__)selfr@   	__class__s     r   rD   zRateLimitMiddleware.__init___   s!    r   r   r   	call_nextr   r   c                h  K   t           j        s ||           d {V S |j        j        }|                    d          s ||           d {V S |dk    r ||           d {V S 	 |j        j        j        j        }t          |          \  }}t          |          }t          t          j                              |z  }d|                    d          dk    r|                    d          d         nd d| d| }	|                    |	           d {V }
|
dk    r|                    |	|d	z             d {V  |
|k    rHt"                              d
|||
|           t'          dd| d||ddt)          |          i          S  ||           d {V }t)          |          |j        d<   t)          t-          d||
z
                      |j        d<   |S # t.          $ rD}t"                              dt)          |                      ||           d {V cY d }~S d }~ww xY w)Nz/api/ai/z/healthzrl:/r&   api:r$      rate_limit_exceeded)r   
identifiercountlimiti  u   请求频率超出限制，请 u    秒后重试。)detailrP   window_secondszRetry-After)status_codecontentr*   zX-RateLimit-Limitr   zX-RateLimit-Remainingrate_limit_redis_error)error)r
   rate_limit_enabledurlr   r   r@   stateredis_clientrawr   r=   inttimerO   r.   increxpireloggerwarningr   r   r*   maxr2   )rE   r   rG   r   redisr   r   rN   bucketkeycurrentresponseexcs                r   dispatchzRateLimitMiddleware.dispatchb   s     * 	,"7+++++++++ {z** 	,"7+++++++++ 9"7+++++++++'	,K%26E(..OGV,W55J %%/Fedjjoo.B.B

3**eePZee]ceeC!JJsOO++++++G!||ll3
333333333  ))!!     $ #"\F"\"\"\!(*0 
 +CKK8    'Yw////////H47LLH018;C7WCT<U<U8V8VH45O 	, 	, 	,NN33s88NDDD"7++++++++++++++	,s&   *D&G# AG# #
H1-9H,&H1,H1)r@   r	   r   rA   )r   r   rG   r   r   r   )__name__
__module____qualname____doc__rD   ri   __classcell__)rF   s   @r   r?   r?   V   s[              4, 4, 4, 4, 4, 4, 4, 4,r   r?   )r   r   r   r   )r   r   r   r   )!rm   
__future__r   r]   typingr   fastapir   r   fastapi.responsesr   starlette.middleware.baser   starlette.typesr	   
app.configr
   app.utils.loggerr   rj   r`   rate_limit_research_maxrate_limit_research_windowrate_limit_search_maxrate_limit_search_windowr   __annotations__rate_limit_default_maxrate_limit_default_windowr   r   r=   r?    r   r   <module>r      s}    2 # " " " " "        % % % % % % % % * * * * * * 8 8 8 8 8 8 # # # # # #       ' ' ' ' ' '	H		 H<h>ab(8(:[\"'    
 183UV   8 8 8 84@, @, @, @, @,, @, @, @, @, @,r   