让成交卡等报价文件真正发出后再出现,并收紧 PDF 顶部留白,避免抬头被裁或空一截。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
"""
|
||||
复制工单号 H5 的短命 token。
|
||||
|
||||
本文件职责:签发/校验 token → 工单号;URL 只带 token,禁止带 userid/工单号。
|
||||
优先 Redis(HTTP 与 Worker 不同进程必须共享);单测无 Redis 时用内存。
|
||||
禁止:当六态;禁止把工单号拼进公网路径。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import secrets
|
||||
import threading
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from agent.config import get_settings
|
||||
from agent.redis_coord.keys import TOKEN_COPY_TICKET
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 销售可能隔几天才点卡;7 天内同一 token 可重复打开复制。
|
||||
COPY_TTL_SECONDS = 7 * 24 * 60 * 60
|
||||
_FORM_FALLBACK = "https://ai.ytd-scm.com/inquiry-form"
|
||||
|
||||
_MEM: dict[str, tuple[str, float]] = {}
|
||||
_MEM_LOCK = threading.Lock()
|
||||
|
||||
|
||||
def issue_copy_token(work_order_no: str, *, ttl_seconds: int = COPY_TTL_SECONDS) -> str:
|
||||
"""
|
||||
签发复制页 token。
|
||||
|
||||
副作用:写 Redis 或内存。工单号为空则返回空串,调用方不要出跳转按钮。
|
||||
"""
|
||||
wo = (work_order_no or "").strip()
|
||||
if not wo:
|
||||
return ""
|
||||
token = secrets.token_urlsafe(24)
|
||||
if _redis_set(token, wo, ttl_seconds):
|
||||
return token
|
||||
expires = time.time() + ttl_seconds
|
||||
with _MEM_LOCK:
|
||||
_MEM[token] = (wo, expires)
|
||||
logger.info("copy token 走内存 wo=%s(HTTP/Worker 分进程时页面会找不到)", wo)
|
||||
return token
|
||||
|
||||
|
||||
def lookup_copy_token(token: str) -> Optional[str]:
|
||||
"""校验 token 未过期;返回工单号或 None。不消费,可重复打开。"""
|
||||
raw = (token or "").strip()
|
||||
if not raw:
|
||||
return None
|
||||
wo = _redis_get(raw)
|
||||
if wo:
|
||||
return wo
|
||||
now = time.time()
|
||||
with _MEM_LOCK:
|
||||
hit = _MEM.get(raw)
|
||||
if not hit:
|
||||
return None
|
||||
wo, exp = hit
|
||||
if exp < now:
|
||||
_MEM.pop(raw, None)
|
||||
return None
|
||||
return wo
|
||||
|
||||
|
||||
def build_copy_ticket_url(work_order_no: str) -> str:
|
||||
"""
|
||||
拼复制页绝对地址:{inquiry-form}/copy/{token}。
|
||||
|
||||
域名来自环境变量,禁止写死测试/正式切换。缺省回合同域名,保证企微能打开。
|
||||
"""
|
||||
token = issue_copy_token(work_order_no)
|
||||
if not token:
|
||||
return ""
|
||||
return f"{_form_base()}/copy/{token}"
|
||||
|
||||
|
||||
def _form_base() -> str:
|
||||
settings = get_settings()
|
||||
form = (settings.inquiry_form_public_base_url or "").strip().rstrip("/")
|
||||
if form:
|
||||
return form
|
||||
pub = (settings.public_base_url or "").strip().rstrip("/")
|
||||
if pub:
|
||||
return f"{pub}/inquiry-form"
|
||||
return _FORM_FALLBACK
|
||||
|
||||
|
||||
def _redis_set(token: str, work_order_no: str, ttl_seconds: int) -> bool:
|
||||
client = _try_redis()
|
||||
if client is None:
|
||||
return False
|
||||
try:
|
||||
client.raw.set(client.key(TOKEN_COPY_TICKET, token), work_order_no, ex=ttl_seconds)
|
||||
return True
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.warning("copy token Redis 写入失败:%s", exc)
|
||||
return False
|
||||
|
||||
|
||||
def _redis_get(token: str) -> Optional[str]:
|
||||
client = _try_redis()
|
||||
if client is None:
|
||||
return None
|
||||
try:
|
||||
val = client.raw.get(client.key(TOKEN_COPY_TICKET, token))
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.warning("copy token Redis 读取失败:%s", exc)
|
||||
return None
|
||||
if val is None:
|
||||
return None
|
||||
text = val if isinstance(val, str) else val.decode("utf-8")
|
||||
return text.strip() or None
|
||||
|
||||
|
||||
def _try_redis():
|
||||
"""有 Redis 才用;单测 memory 后端也可用,保证同进程能读到。"""
|
||||
try:
|
||||
from agent.redis_coord import get_redis_runtime
|
||||
|
||||
return get_redis_runtime(get_settings()).client
|
||||
except Exception: # noqa: BLE001
|
||||
return None
|
||||
Reference in New Issue
Block a user