from pathlib import Path import shutil import hashlib import json root = Path('C:/kefu/wechat_rpa') out = Path('C:/kefu/deploy/patient-session-20260918') manifest = [] def edit(name, transform): path = root / name raw = path.read_bytes() text = raw.decode('utf-8').replace('\r\n', '\n') changed = transform(text) backup = out / 'before-backend' / name backup.parent.mkdir(parents=True, exist_ok=True) if backup.exists(): raise RuntimeError('Refuse to overwrite backup: ' + str(backup)) shutil.copy2(path, backup) newline = '\r\n' if b'\r\n' in raw else '\n' path.write_bytes(changed.replace('\n', newline).encode('utf-8')) manifest.append({'path': str(path), 'backup': str(backup), 'before_sha256': hashlib.sha256(raw).hexdigest(), 'after_sha256': hashlib.sha256(path.read_bytes()).hexdigest()}) def patient(text): marker = '\ndef mask_mobile(value: Any) -> str:' assert marker in text text = text.replace(marker, ''' class ZytPatientSessionExpired(ZytPatientError): """The upstream patient session needs reauthentication, not desktop logout.""" class ZytPatientPermissionDenied(ZytPatientError): """The authenticated ZYT account cannot query patients.""" def _session_expired_message(message: str) -> bool: normalized = str(message or "").lower().replace(" ", "") return any(part in normalized for part in ( "登录超时", "登录已失效", "登录失效", "登录已过期", "登录过期", "请重新登录", "请先登录", "token过期", "token已过期", "token失效", "token已失效", "tokenexpired", "unauthorized", )) ''' + marker, 1) text = text.replace('raise ZytPatientError("客户端登录已失效,请重新打开软件登录")', 'raise ZytPatientSessionExpired("ZYT 患者查询会话缺失,请重新验证账号")', 1) old = ''' if exc.code in {401, 403}: raise ZytPatientError("ZYT 登录或患者查询权限已失效,请重新登录") from exc''' assert old in text text = text.replace(old, ''' if exc.code == 401: raise ZytPatientSessionExpired("ZYT 患者查询登录已过期,请重新验证账号") from exc if exc.code == 403: raise ZytPatientPermissionDenied("当前 ZYT 账号没有患者查询权限") from exc''', 1) old = ''' if not isinstance(payload, dict) or int(payload.get("code") or 0) != 1: message = str(payload.get("msg") if isinstance(payload, dict) else "").strip() raise ZytPatientError(message or "ZYT 患者查询失败")''' assert old in text text = text.replace(old, ''' if not isinstance(payload, dict) or str(payload.get("code")) != "1": message = str(payload.get("msg") or "").strip() if isinstance(payload, dict) else "" if "权限" in message or "无权" in message: raise ZytPatientPermissionDenied(message) if (isinstance(payload, dict) and str(payload.get("code")) == "-1") or _session_expired_message(message): raise ZytPatientSessionExpired(message or "ZYT 患者查询会话已失效") raise ZytPatientError(message or "ZYT 患者查询失败")''', 1) return text def api(text): text = text.replace('from zyt_patient_client import ZytPatientClient, ZytPatientError', 'from zyt_patient_client import (\n ZytPatientClient, ZytPatientError, ZytPatientSessionExpired, ZytPatientPermissionDenied,\n)', 1) marker = ' if isinstance(exc, ZytPatientError):' assert marker in text text = text.replace(marker, ''' if isinstance(exc, ZytPatientSessionExpired): return HTTPException(status_code=409, detail={ "code": "zyt_session_expired", "message": "ZYT 患者查询登录已过期,请重新验证账号。", }) if isinstance(exc, ZytPatientPermissionDenied): return HTTPException(status_code=403, detail=str(exc)) ''' + marker, 1) return text def store(text): marker = 'from archive_conversation_filter import should_exclude_conversation' text = text.replace(marker, marker + '\nfrom zyt_patient_client import ZytPatientSessionExpired', 1) text = text.replace('raise RuntimeError("该客户端账号尚未提供 ZYT 查询会话,请重新打开软件登录")', 'raise ZytPatientSessionExpired("该客户端账号尚未提供 ZYT 查询会话,请重新验证账号")', 1) text = text.replace('raise RuntimeError("ZYT 查询会话无法解密,请重新打开软件登录") from exc', 'raise ZytPatientSessionExpired("ZYT 查询会话无法解密,请重新验证账号") from exc', 1) marker = ' def clear_zyt_session(self, tenant_id: str) -> None:' addition = ''' def refresh_zyt_session( self, desktop_token: str, account_id: int, tenant_id: str, zyt_token: str, identity: dict[str, Any], ) -> None: """Update upstream credentials and permissions under the existing session. Recheck revocation after the network verification inside the same write transaction. Never create, un-revoke, or change ownership of a desktop account/device/session while refreshing the independent ZYT session. """ import time import secret_box from admin_backend import token_hash tenant = safe_scope(tenant_id, label="租户") zyt_token = str(zyt_token or "").strip() user_id = str(identity.get("user_id") or "").strip() if not zyt_token or not user_id: raise ValueError("ZYT 身份信息不完整") permissions_raw = identity.get("permissions") permissions = sorted({str(value).strip() for value in permissions_raw if str(value).strip()}) if isinstance(permissions_raw, list) else [] role_ids = [] for value in identity.get("role_ids") if isinstance(identity.get("role_ids"), list) else []: try: role_ids.append(int(value)) except (TypeError, ValueError): continue is_root = int(identity.get("root") in (True, 1, "1") or "*" in permissions) known = int(bool(identity.get("permissions_known", isinstance(permissions_raw, list)))) encrypted = secret_box.encrypt(zyt_token, self.database._secret_key()) stamp = utc_now() with self.database.connect() as db: db.execute("BEGIN IMMEDIATE") account = db.execute( """SELECT a.id,a.zyt_user_id,a.tenant_id FROM desktop_sessions s JOIN desktop_accounts a ON a.id=s.account_id JOIN desktop_devices d ON d.id=s.device_row_id AND d.account_id=a.id WHERE s.token_digest=? AND s.revoked_at='' AND s.expires_at>=? AND a.id=? AND a.tenant_id=? AND a.status='active' AND d.revoked_at=''""", (token_hash(str(desktop_token or "")), int(time.time()), int(account_id), tenant), ).fetchone() if account is None: raise PermissionError("桌面登录已失效,请重新登录") if str(account["zyt_user_id"]) != user_id: raise ValueError("请使用当前桌面账号重新验证,不能切换到其他账号") db.execute( """UPDATE desktop_accounts SET zyt_root=?,zyt_role_ids_json=?, zyt_permissions_json=?,zyt_permissions_known=?,updated_at=? WHERE id=?""", (is_root, json.dumps(sorted(set(role_ids))), json.dumps(permissions, ensure_ascii=False), known, stamp, int(account_id)), ) db.execute( """INSERT INTO archive_zyt_session (tenant_id,zyt_user_id,token_enc,refreshed_at) VALUES (?,?,?,?) ON CONFLICT(tenant_id) DO UPDATE SET zyt_user_id=excluded.zyt_user_id,token_enc=excluded.token_enc, refreshed_at=excluded.refreshed_at""", (tenant, user_id, encrypted, stamp), ) db.commit() ''' assert marker in text return text.replace(marker, addition + marker, 1) def admin(text): marker = '\nclass DesktopAccountBody(BaseModel):' assert marker in text text = text.replace(marker, ''' class DesktopZytSessionBody(BaseModel): zyt_token: str = Field(min_length=1, max_length=8192, repr=False) ''' + marker, 1) marker = ' @app.get("/api/v2/desktop/me")' addition = ''' @app.post("/api/v2/desktop/auth/zyt-session") async def desktop_refresh_zyt_session( body: DesktopZytSessionBody, request: Request, account: Any = Depends(current_desktop), ) -> dict: raw = request.headers.get("authorization", "") desktop_token = raw[7:].strip() if raw.lower().startswith("bearer ") else "" if zyt_token_verifier is not None: verifier = zyt_token_verifier else: login_api_url, _configured = desktop_login_api_url() verifier = lambda token: zyt_auth.verify_desktop_token(token, api_url=login_api_url) try: identity = await asyncio.to_thread(verifier, body.zyt_token.strip()) if (not isinstance(identity, dict) or not identity.get("user_id") or str(identity.get("terminal")) != str(zyt_auth.WECOM_RPA_TERMINAL) or str(identity.get("status") or "") != "active"): raise zyt_auth.ZytAuthError("ZYT 桌面身份验证失败,请重新验证账号") except zyt_auth.ZytAuthError as exc: raise HTTPException(status_code=409, detail={ "code": "zyt_session_expired", "message": "ZYT 患者查询登录已过期,请重新验证账号。", }) from exc if str(identity["user_id"]).strip() != str(account["zyt_user_id"]): raise HTTPException(status_code=403, detail="请使用当前桌面账号重新验证,不能切换到其他账号") archive_store = getattr(app.state, "archive_store", None) if archive_store is None: raise HTTPException(status_code=503, detail="患者查询服务暂不可用") try: await asyncio.to_thread( archive_store.refresh_zyt_session, desktop_token, int(account["id"]), str(account["tenant_id"]), body.zyt_token, identity, ) except PermissionError as exc: raise HTTPException(status_code=401, detail=str(exc)) from exc except ValueError as exc: raise HTTPException(status_code=403, detail=str(exc)) from exc return {"ok": True} ''' assert marker in text return text.replace(marker, addition + marker, 1) for name, transform in (('zyt_patient_client.py', patient), ('archive_api.py', api), ('archive_store.py', store), ('admin_api.py', admin)): edit(name, transform) (out / 'backend-manifest.json').write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding='utf-8') print('Patient session recovery updated in source only; original files backed up.')