fix:优化安装源
This commit is contained in:
20
Dockerfile
20
Dockerfile
@@ -26,7 +26,25 @@ COPY --from=build /app/dist ./dist
|
||||
COPY --from=build /app/public ./public
|
||||
|
||||
COPY backend/requirements.txt ./backend/requirements.txt
|
||||
RUN pip3 install --no-cache-dir --break-system-packages -r backend/requirements.txt
|
||||
ARG PIP_INDEX="https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
ARG PIP_TRUSTED_HOST="pypi.tuna.tsinghua.edu.cn"
|
||||
RUN pip3 install --no-cache-dir --break-system-packages \
|
||||
-i "${PIP_INDEX}" --trusted-host "${PIP_TRUSTED_HOST}" \
|
||||
fastapi==0.115.0 && \
|
||||
pip3 install --no-cache-dir --break-system-packages \
|
||||
-i "${PIP_INDEX}" --trusted-host "${PIP_TRUSTED_HOST}" \
|
||||
"uvicorn[standard]==0.30.0" && \
|
||||
pip3 install --no-cache-dir --break-system-packages \
|
||||
-i "${PIP_INDEX}" --trusted-host "${PIP_TRUSTED_HOST}" \
|
||||
httpx==0.27.0 && \
|
||||
pip3 install --no-cache-dir --break-system-packages \
|
||||
-i "${PIP_INDEX}" --trusted-host "${PIP_TRUSTED_HOST}" \
|
||||
"websockets>=12.0" && \
|
||||
pip3 install --no-cache-dir --break-system-packages \
|
||||
-i "${PIP_INDEX}" --trusted-host "${PIP_TRUSTED_HOST}" \
|
||||
"openai>=1.0.0" && \
|
||||
pip3 check && \
|
||||
python3 -c "import fastapi; import uvicorn; import httpx; import websockets; import openai; print('all deps ok')"
|
||||
|
||||
COPY backend ./backend
|
||||
COPY .env.example ./
|
||||
|
||||
@@ -1483,5 +1483,104 @@
|
||||
"new_msg_id": 6612157681502055018
|
||||
},
|
||||
"type": "message"
|
||||
},
|
||||
{
|
||||
"key": "HBpEnbtj9BJZ",
|
||||
"message": {
|
||||
"msg_id": 1649765637,
|
||||
"from_user_name": {
|
||||
"str": "wxid_f2q8xscgg31322"
|
||||
},
|
||||
"to_user_name": {
|
||||
"str": "wxid_f2q8xscgg31322"
|
||||
},
|
||||
"msg_type": 51,
|
||||
"content": {
|
||||
"str": "<msg>\n<op id='2'>\n<username>wxid_f2q8xscgg31322</username>\n<name>lastMessage</name>\n<arg>{\"messageSvrId\":\"8354732942085133458\",\"MsgCreateTime\":\"1773163308\"}</arg>\n</op>\n</msg>"
|
||||
},
|
||||
"status": 3,
|
||||
"img_status": 1,
|
||||
"img_buf": {
|
||||
"len": 0
|
||||
},
|
||||
"create_time": 1773192394,
|
||||
"msg_source": "<msgsource>\n\t<signature>v1_bhcJTcNo</signature>\n\t<tmp_node>\n\t\t<publisher-id></publisher-id>\n\t</tmp_node>\n</msgsource>\n",
|
||||
"new_msg_id": 3249105399159457776
|
||||
},
|
||||
"type": "message"
|
||||
},
|
||||
{
|
||||
"key": "HBpEnbtj9BJZ",
|
||||
"message": {
|
||||
"msg_id": 453179271,
|
||||
"from_user_name": {
|
||||
"str": "wxid_f2q8xscgg31322"
|
||||
},
|
||||
"to_user_name": {
|
||||
"str": "newsapp"
|
||||
},
|
||||
"msg_type": 51,
|
||||
"content": {
|
||||
"str": "<msg>\n<op id='2'>\n<username>newsapp</username>\n<name>lastMessage</name>\n<arg>{\"messageSvrId\":\"1453521873\",\"MsgCreateTime\":\"1773192299\"}</arg>\n</op>\n</msg>"
|
||||
},
|
||||
"status": 3,
|
||||
"img_status": 1,
|
||||
"img_buf": {
|
||||
"len": 0
|
||||
},
|
||||
"create_time": 1773192400,
|
||||
"msg_source": "<msgsource>\n\t<signature>v1_olpnsmGt</signature>\n\t<tmp_node>\n\t\t<publisher-id></publisher-id>\n\t</tmp_node>\n</msgsource>\n",
|
||||
"new_msg_id": 5288934263040164256
|
||||
},
|
||||
"type": "message"
|
||||
},
|
||||
{
|
||||
"key": "HBpEnbtj9BJZ",
|
||||
"message": {
|
||||
"msg_id": 5670552,
|
||||
"from_user_name": {
|
||||
"str": "wxid_f2q8xscgg31322"
|
||||
},
|
||||
"to_user_name": {
|
||||
"str": "zhang499142409"
|
||||
},
|
||||
"msg_type": 51,
|
||||
"content": {
|
||||
"str": "<msg>\n<op id='2'>\n<username>zhang499142409</username>\n<name>lastMessage</name>\n<arg>{\"messageSvrId\":\"6612157681502055018\",\"MsgCreateTime\":\"1773163339\"}</arg>\n</op>\n</msg>"
|
||||
},
|
||||
"status": 3,
|
||||
"img_status": 1,
|
||||
"img_buf": {
|
||||
"len": 0
|
||||
},
|
||||
"create_time": 1773192408,
|
||||
"msg_source": "<msgsource>\n\t<signature>v1_aMGpPuti</signature>\n\t<tmp_node>\n\t\t<publisher-id></publisher-id>\n\t</tmp_node>\n</msgsource>\n",
|
||||
"new_msg_id": 950905004367894098
|
||||
},
|
||||
"type": "message"
|
||||
},
|
||||
{
|
||||
"key": "HBpEnbtj9BJZ",
|
||||
"message": {
|
||||
"msg_id": 1773181860,
|
||||
"from_user_name": {
|
||||
"str": "newsapp"
|
||||
},
|
||||
"to_user_name": {
|
||||
"str": "wxid_f2q8xscgg31322"
|
||||
},
|
||||
"msg_type": 10002,
|
||||
"content": {
|
||||
"str": "<?xml version=\"1.0\"?>\n<sysmsg type=\"functionmsg\">\n\t<functionmsg>\n\t\t<cgi>/cgi-bin/micromsg-bin/addtxnewsmsg</cgi>\n\t\t<cmdid>825</cmdid>\n\t\t<businessid>50001</businessid>\n\t\t<functionmsgid>2026031100</functionmsgid>\n\t\t<op>0</op>\n\t\t<version>1773181804</version>\n\t\t<retryinterval>150</retryinterval>\n\t\t<reportid>63162</reportid>\n\t\t<successkey>0</successkey>\n\t\t<failkey>1</failkey>\n\t\t<finalfailkey>2</finalfailkey>\n\t\t<custombuff>CAAQ\nAzjttsLNBkDstsLNBkikt8LNBlAAwAEB</custombuff>\n\t\t<retrycount>3</retrycount>\n\t</functionmsg>\n</sysmsg>\n"
|
||||
},
|
||||
"status": 3,
|
||||
"img_status": 1,
|
||||
"img_buf": {
|
||||
"len": 0
|
||||
},
|
||||
"create_time": 1773193510,
|
||||
"new_msg_id": 1773181860
|
||||
},
|
||||
"type": "message"
|
||||
}
|
||||
]
|
||||
@@ -301,14 +301,16 @@ async def get_login_qrcode(body: QrCodeRequest):
|
||||
body_text,
|
||||
)
|
||||
data = resp.json()
|
||||
# 第一步:记录完整返回并保存 Data62,供第二步滑块自动填充参数
|
||||
# 保存 Data62(顶层 "Data62"),以 d000 标识移除尾部乱码
|
||||
try:
|
||||
data62 = data.get("Data62") or (data.get("Data") or {}).get("data62") or ""
|
||||
data62 = (data.get("Data62") or "").strip()
|
||||
if not data62 and isinstance(data.get("Data"), dict):
|
||||
data62 = (data.get("Data").get("Data62") or data.get("Data").get("data62") or "").strip()
|
||||
data62 = _clean_data62(data62)
|
||||
qrcode_store[key] = {"data62": data62, "response": data}
|
||||
# 在返回中拼接已存储标记,便于后续步骤使用同一 key 取 data62
|
||||
data["_data62_stored"] = True
|
||||
data["_data62_length"] = len(data62)
|
||||
logger.info("Stored Data62 for key=%s (len=%s)", key, len(data62))
|
||||
logger.info("Stored Data62 for key=%s (len=%s) from GetLoginQrCodeNewDirect top-level", key, len(data62))
|
||||
except Exception as e:
|
||||
logger.warning("Store qrcode data for key=%s failed: %s", key, e)
|
||||
return data
|
||||
@@ -364,6 +366,23 @@ def _extract_clean_ticket(obj: dict) -> Optional[str]:
|
||||
return "".join(clean) if clean else None
|
||||
|
||||
|
||||
def _clean_data62(s: str) -> str:
|
||||
"""去掉 Data62 尾部乱码:以 d000 标识乱码起始,截断保留此前有效内容。"""
|
||||
if not s or not isinstance(s, str):
|
||||
return ""
|
||||
s = s.strip()
|
||||
idx = s.find("d0000000000000101000000000000000d0000000000000000000000000000007f")
|
||||
if idx != -1:
|
||||
return s[:idx].strip()
|
||||
idx = s.find("d0000000000000101")
|
||||
if idx != -1:
|
||||
return s[:idx].strip()
|
||||
idx = s.find("d00000000")
|
||||
if idx != -1:
|
||||
return s[:idx].strip()
|
||||
return s
|
||||
|
||||
|
||||
@app.get("/auth/scan-status")
|
||||
async def check_scan_status(
|
||||
key: str = Query(..., description="账号唯一标识"),
|
||||
@@ -389,9 +408,11 @@ async def check_scan_status(
|
||||
data = resp.json()
|
||||
ticket = _extract_clean_ticket(data)
|
||||
if ticket:
|
||||
# 不调用滑块服务;返回自带预填表单的页面 path,iframe 加载后自动填充 Key/Data62/Original Ticket,用户点「开始验证」提交到第三方 7765
|
||||
# data62 必须来自 GetLoginQrCodeNewDirect 返回的顶层 "Data62",不能使用 CheckLoginStatus 里的 data62(常为空);并去掉尾部乱码
|
||||
stored = qrcode_store.get(key) or {}
|
||||
data62 = stored.get("data62") or ""
|
||||
data62 = _clean_data62(stored.get("data62") or "")
|
||||
if not data62:
|
||||
data62 = _clean_data62(data.get("Data62") or (data.get("Data") or {}).get("Data62") or (data.get("Data") or {}).get("data62") or "")
|
||||
params = {"key": SLIDER_VERIFY_KEY, "ticket": ticket}
|
||||
if data62:
|
||||
params["data62"] = data62
|
||||
@@ -453,9 +474,65 @@ async def slider_form(
|
||||
ticket: str = Query(..., description="Original Ticket"),
|
||||
):
|
||||
"""返回带 Key/Data62/Original Ticket 预填的表单页,提交到第三方 7765,供 iframe 加载并自动填充。"""
|
||||
data62 = _clean_data62(data62)
|
||||
return HTMLResponse(content=_slider_form_html(key, data62, ticket))
|
||||
|
||||
|
||||
# ---------- 滑块验证提交接口(代理 7765) ----------
|
||||
# 7765 页面提交为 GET:action=SLIDER_VERIFY_BASE_URL,参数 key、data62、original_ticket
|
||||
@app.get("/api/slider-verify")
|
||||
async def api_slider_verify_get(
|
||||
key: str = Query(..., description="Key"),
|
||||
data62: str = Query("", description="Data62"),
|
||||
original_ticket: str = Query(..., description="Original Ticket(与 ticket 二选一)"),
|
||||
ticket: str = Query("", description="Original Ticket(与 original_ticket 二选一)"),
|
||||
):
|
||||
"""代理 7765 滑块提交:GET 转发到 http://113.44.162.180:7765/?key=&data62=&original_ticket=,返回上游响应。"""
|
||||
ticket_val = original_ticket or ticket
|
||||
if not ticket_val:
|
||||
raise HTTPException(status_code=400, detail="original_ticket or ticket required")
|
||||
url = SLIDER_VERIFY_BASE_URL.rstrip("/") + "/"
|
||||
params = {"key": key, "data62": _clean_data62(data62), "original_ticket": ticket_val}
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
resp = await client.get(url, params=params)
|
||||
# 返回上游的 body;若为 JSON 则解析后返回
|
||||
try:
|
||||
return resp.json()
|
||||
except Exception:
|
||||
return {"ok": resp.status_code == 200, "status_code": resp.status_code, "text": resp.text[:500]}
|
||||
except Exception as e:
|
||||
logger.warning("Slider verify upstream error: %s", e)
|
||||
raise HTTPException(status_code=502, detail=f"slider_upstream_error: {e}") from e
|
||||
|
||||
|
||||
class SliderVerifyBody(BaseModel):
|
||||
key: str
|
||||
data62: Optional[str] = ""
|
||||
original_ticket: Optional[str] = None
|
||||
ticket: Optional[str] = None
|
||||
|
||||
|
||||
@app.post("/api/slider-verify")
|
||||
async def api_slider_verify_post(body: SliderVerifyBody):
|
||||
"""代理 7765 滑块提交:POST body 转成 GET 请求转发到 7765,返回上游响应。"""
|
||||
ticket_val = body.original_ticket or body.ticket
|
||||
if not ticket_val:
|
||||
raise HTTPException(status_code=400, detail="original_ticket or ticket required")
|
||||
url = SLIDER_VERIFY_BASE_URL.rstrip("/") + "/"
|
||||
params = {"key": body.key, "data62": _clean_data62(body.data62 or ""), "original_ticket": ticket_val}
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
resp = await client.get(url, params=params)
|
||||
try:
|
||||
return resp.json()
|
||||
except Exception:
|
||||
return {"ok": resp.status_code == 200, "status_code": resp.status_code, "text": resp.text[:500]}
|
||||
except Exception as e:
|
||||
logger.warning("Slider verify upstream error: %s", e)
|
||||
raise HTTPException(status_code=502, detail=f"slider_upstream_error: {e}") from e
|
||||
|
||||
|
||||
# ---------- R1-2 客户画像 / R1-3 定时问候 / R1-4 分群推送 / 消息与发送 ----------
|
||||
|
||||
class CustomerCreate(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user