diff --git a/.gitignore b/.gitignore index 0e9c298ff7..040d16a5b7 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,8 @@ sdk/android-app/build/ # >20MB 大文件(GitHub/Gitea 限制) 开发文档/6、后端/github-repos/ sdk/apks/*.apk +apk_analysis/ +sdk/admin/ # 敏感配置 sdk/.env diff --git a/sdk/agent/agent.py b/sdk/agent/agent.py index 463e2c66a7..0a40663a37 100644 --- a/sdk/agent/agent.py +++ b/sdk/agent/agent.py @@ -324,7 +324,32 @@ class WorkPhoneAgent: info = self.d.info status["screen_on"] = info.get("screenOn", False) status["current_app"] = info.get("currentPackageName", "") - except: + + wechat_pkg = "com.tencent.mm" + try: + running = self.d.shell(f"pidof {wechat_pkg}").output.strip() + status["wechat_running"] = bool(running) + status["wechat_pid"] = running if running else None + except Exception: + status["wechat_running"] = False + + status["wechat_foreground"] = ( + status.get("current_app") == wechat_pkg + ) + + try: + net_out = self.d.shell( + "dumpsys connectivity | grep -m1 'type: WIFI\\|type: MOBILE' || echo 'none'" + ).output.strip() + status["network_type"] = ( + "wifi" if "WIFI" in net_out + else "mobile" if "MOBILE" in net_out + else "none" + ) + except Exception: + status["network_type"] = "unknown" + + except Exception: pass return status @@ -371,8 +396,42 @@ class WorkPhoneAgent: try: wifi = self.d.shell("dumpsys wifi | grep 'Wi-Fi is'").output.strip() status["wifi"] = "enabled" in wifi.lower() - except: + ssid_out = self.d.shell( + "dumpsys wifi | grep -m1 'mWifiInfo' || echo ''" + ).output.strip() + if "SSID:" in ssid_out: + import re + m = re.search(r'SSID:\s*([^,]+)', ssid_out) + if m: + status["wifi_ssid"] = m.group(1).strip().strip('"') + ip_out = self.d.shell("ip route | grep -m1 'src'").output.strip() + if "src " in ip_out: + status["ip_address"] = ip_out.split("src ")[-1].split()[0] + except Exception: pass + + # 微信详细状态 + wechat_pkg = "com.tencent.mm" + try: + pid = self.d.shell(f"pidof {wechat_pkg}").output.strip() + status["wechat"] = { + "installed": True, + "running": bool(pid), + "pid": pid or None, + "foreground": info.get("currentPackageName") == wechat_pkg, + } + if pid: + mem = self.d.shell( + f"dumpsys meminfo {wechat_pkg} | grep 'TOTAL PSS' || echo ''" + ).output.strip() + if "TOTAL" in mem: + parts = mem.split() + for p in parts: + if p.replace(",", "").isdigit(): + status["wechat"]["memory_kb"] = int(p.replace(",", "")) + break + except Exception: + status["wechat"] = {"installed": False, "running": False} except Exception as e: status["error"] = str(e) diff --git a/sdk/agent/hook/frida_manager.py b/sdk/agent/hook/frida_manager.py index a7f903e15e..395e1d1b8e 100644 --- a/sdk/agent/hook/frida_manager.py +++ b/sdk/agent/hook/frida_manager.py @@ -28,11 +28,17 @@ logger = logging.getLogger(__name__) SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) DEFAULT_SCRIPT = os.path.join(SCRIPT_DIR, "wechat_hook_v2.js") WECHAT_PACKAGE = "com.tencent.mm" -GADGET_PORT = 27042 +import random as _random +_ANTI_DETECT_PORT_RANGE = (10000, 60000) +GADGET_PORT = _random.randint(*_ANTI_DETECT_PORT_RANGE) ConnectionMode = Literal["usb", "gadget", "remote"] +def _random_port() -> int: + return _random.randint(*_ANTI_DETECT_PORT_RANGE) + + class FridaManager: """Frida 会话管理器,支持 USB / Gadget / Remote 三种连接模式""" @@ -46,8 +52,10 @@ class FridaManager: reconnect_interval: float = 5.0, mode: ConnectionMode = "gadget", gadget_host: str = "127.0.0.1", - gadget_port: int = GADGET_PORT, + gadget_port: int = 0, ): + if gadget_port == 0: + gadget_port = _random_port() self.device_serial = device_serial self.script_path = script_path self.on_event = on_event diff --git a/sdk/agent/skills/base.py b/sdk/agent/skills/base.py index 41f4c6592f..94d8c4df09 100644 --- a/sdk/agent/skills/base.py +++ b/sdk/agent/skills/base.py @@ -1,10 +1,12 @@ """ -Agent端技能基类 +Agent端技能基类(含防封拟人化行为层) """ +import math import time +import random import logging -from typing import Dict, Any, List, Optional +from typing import Dict, Any, List, Optional, Tuple logger = logging.getLogger(__name__) @@ -25,6 +27,119 @@ class BaseSkill: """ self.d = device self.bus = bus + + # ========================================================== + # 防封拟人化行为层(所有 Skill 操作必须使用这些方法) + # ========================================================== + + @staticmethod + def human_delay(min_sec: float = 0.5, max_sec: float = 3.0): + """拟人延迟:高斯分布,均值在 min-max 中间,避免机械化固定间隔""" + mean = (min_sec + max_sec) / 2 + std = (max_sec - min_sec) / 6 + delay = max(min_sec, min(max_sec, random.gauss(mean, std))) + time.sleep(delay) + + def human_type(self, text: str, clear: bool = True): + """拟人输入:逐字输入 + 随机间隔 + 偶尔打错重输""" + if clear: + self.d.clear_text() + self.human_delay(0.2, 0.5) + for i, char in enumerate(text): + self.d.send_keys(char) + time.sleep(random.uniform(0.04, 0.18)) + if random.random() < 0.03 and i < len(text) - 1: + wrong = random.choice("abcdefg1234") + self.d.send_keys(wrong) + time.sleep(random.uniform(0.1, 0.25)) + self.d.press("del") + time.sleep(random.uniform(0.08, 0.15)) + + def human_click(self, x: int, y: int, offset: int = 8): + """拟人点击:加微小随机偏移,避免每次坐标完全一致""" + dx = random.randint(-offset, offset) + dy = random.randint(-offset, offset) + self.d.click(x + dx, y + dy) + self.human_delay(0.1, 0.4) + + def human_swipe( + self, + start: Tuple[int, int], + end: Tuple[int, int], + duration: Optional[float] = None, + ): + """拟人滑动:贝塞尔曲线轨迹 + 随机中间控制点 + 随机持续时间""" + if duration is None: + duration = random.uniform(0.3, 0.8) + ctrl_x = (start[0] + end[0]) // 2 + random.randint(-30, 30) + ctrl_y = (start[1] + end[1]) // 2 + random.randint(-30, 30) + steps = max(8, int(duration * 40)) + points = [] + for i in range(steps + 1): + t = i / steps + bx = (1 - t) ** 2 * start[0] + 2 * (1 - t) * t * ctrl_x + t ** 2 * end[0] + by = (1 - t) ** 2 * start[1] + 2 * (1 - t) * t * ctrl_y + t ** 2 * end[1] + points.append((int(bx), int(by))) + try: + self.d.swipe_points(points, duration) + except (AttributeError, TypeError): + self.d.swipe(start[0], start[1], end[0], end[1], duration=duration) + + def random_browse(self, duration_sec: float = 30): + """随机浏览:模拟真人无目的翻看,用于养号/填充自然行为""" + try: + info = self.d.info + w = info.get("displayWidth", 1080) + h = info.get("displayHeight", 2400) + except Exception: + w, h = 1080, 2400 + end_time = time.time() + duration_sec + while time.time() < end_time: + action = random.choice(["scroll", "pause", "scroll", "pause", "tap_safe"]) + if action == "scroll": + self.human_swipe((w // 2, int(h * 0.75)), (w // 2, int(h * 0.25))) + self.human_delay(1.5, 4.0) + elif action == "pause": + self.human_delay(2.0, 6.0) + elif action == "tap_safe": + safe_x = random.randint(int(w * 0.1), int(w * 0.9)) + safe_y = random.randint(int(h * 0.2), int(h * 0.7)) + self.human_click(safe_x, safe_y, offset=5) + self.human_delay(1.0, 3.0) + self.d.press("back") + self.human_delay(0.5, 1.5) + + def natural_behavior_before(self, action: str = "send_message"): + """ + 自然行为链前置:在执行关键操作前模拟真人浏览行为。 + 打开对话 → 浏览历史(上滑) → 停顿 → 再执行操作 + 30%概率触发;send_message/add_friend/post_moments 触发率更高(50%) + """ + high_risk = {"send_message", "add_friend", "post_moments", "batch_send", "mass_send"} + trigger_rate = 0.5 if action in high_risk else 0.3 + if random.random() > trigger_rate: + return + try: + info = self.d.info + w = info.get("displayWidth", 1080) + h = info.get("displayHeight", 2400) + except Exception: + w, h = 1080, 2400 + self.human_delay(0.8, 2.0) + if random.random() < 0.6: + self.human_swipe((w // 2, int(h * 0.3)), (w // 2, int(h * 0.7))) + self.human_delay(1.0, 3.0) + self.human_delay(0.5, 1.5) + + def natural_behavior_after(self, action: str = "send_message"): + """ + 自然行为链后置:操作完成后随机穿插自然行为。 + 30%概率浏览朋友圈/首页等 + """ + if random.random() > 0.3: + return + self.human_delay(1.0, 3.0) + self.random_browse(duration_sec=random.uniform(5, 15)) def say(self, message: str, to_skill: Optional[str] = None, data: Optional[Dict[str, Any]] = None): """向总线发一条消息,其它 Skill 可通过 read_chat 看到""" diff --git a/sdk/agent/skills/wechat/skill.py b/sdk/agent/skills/wechat/skill.py index 10633c1752..316742234e 100644 --- a/sdk/agent/skills/wechat/skill.py +++ b/sdk/agent/skills/wechat/skill.py @@ -128,6 +128,7 @@ class WechatSkill(BaseSkill): """ 发送消息(先尝试视觉流程,失败则用规则流程) """ + self.natural_behavior_before("send_message") start = time.time() try: out = self.send_message_with_vision(to_id, content, msg_type) @@ -347,6 +348,7 @@ class WechatSkill(BaseSkill): def add_friend(self, user_id: str, message: str = "") -> Dict[str, Any]: """添加好友""" + self.natural_behavior_before("add_friend") try: self.launch() self.sleep(2) diff --git a/sdk/app/routers/connection.py b/sdk/app/routers/connection.py index b817bd9067..c134cadba4 100644 --- a/sdk/app/routers/connection.py +++ b/sdk/app/routers/connection.py @@ -124,18 +124,22 @@ async def get_connection_status() -> Dict[str, Any]: for item in online_devices: device_id = item.get("device_id", "") last_heartbeat = item.get("last_heartbeat", "") - device_rows.append( - { - "device_id": device_id, - "project_id": item.get("project_id", ""), - "model": item.get("model", ""), - "platform": item.get("platform", ""), - "status": item.get("status", "online"), - "last_heartbeat": last_heartbeat, - "heartbeat_age_seconds": _heartbeat_age_seconds(last_heartbeat), - "capabilities": item.get("capabilities", []), - } - ) + row = { + "device_id": device_id, + "project_id": item.get("project_id", ""), + "model": item.get("model", ""), + "platform": item.get("platform", ""), + "status": item.get("status", "online"), + "last_heartbeat": last_heartbeat, + "heartbeat_age_seconds": _heartbeat_age_seconds(last_heartbeat), + "capabilities": item.get("capabilities", []), + "wechat_running": item.get("wechat_running"), + "wechat_foreground": item.get("wechat_foreground"), + "network_type": item.get("network_type"), + "screen_on": item.get("screen_on"), + "current_app": item.get("current_app"), + } + device_rows.append(row) return { "code": 200, diff --git a/sdk/app/services/account_lifecycle.py b/sdk/app/services/account_lifecycle.py new file mode 100644 index 0000000000..6e799fd956 --- /dev/null +++ b/sdk/app/services/account_lifecycle.py @@ -0,0 +1,169 @@ +""" +防封模块 — 账号生命周期管理 +管理账号从注册到成熟的各阶段操作限制。 + +阶段定义: + - 新号期 (0-7天):严格限制,以养号为主 + - 成长期 (8-30天):逐步放宽,日常操作 + - 成熟期 (30天+):正常使用限制 +""" + +import logging +import time +from enum import Enum +from typing import Dict, Optional + +logger = logging.getLogger(__name__) + +_redis = None +_redis_last_check: float = 0.0 +_REDIS_CHECK_INTERVAL = 60.0 + + +async def _get_redis(): + global _redis, _redis_last_check + now = time.time() + if _redis is not None: + if now - _redis_last_check > _REDIS_CHECK_INTERVAL: + try: + await _redis.ping() + _redis_last_check = now + except Exception: + logger.warning("Redis 连接失效,重置") + _redis = None + if _redis is not None: + return _redis + try: + import redis.asyncio as aioredis + from config import settings + _redis = aioredis.from_url(settings.REDIS_URL, decode_responses=True) + await _redis.ping() + _redis_last_check = now + return _redis + except Exception: + _redis = None + return None + + +_memory_accounts: Dict[str, dict] = {} + + +class AccountPhase(str, Enum): + NEW = "new" # 0-7天 + GROWING = "growing" # 8-30天 + MATURE = "mature" # 30天+ + + +PHASE_RULES = { + AccountPhase.NEW: { + "max_daily_add_friend": 10, + "max_daily_send_message": 20, + "max_daily_post_moments": 2, + "max_daily_group_send": 5, + "required_warm_up": True, + "description": "新号期:严格限制,优先养号", + }, + AccountPhase.GROWING: { + "max_daily_add_friend": 30, + "max_daily_send_message": 80, + "max_daily_post_moments": 4, + "max_daily_group_send": 30, + "required_warm_up": False, + "description": "成长期:逐步放宽", + }, + AccountPhase.MATURE: { + "max_daily_add_friend": 50, + "max_daily_send_message": 200, + "max_daily_post_moments": 5, + "max_daily_group_send": 50, + "required_warm_up": False, + "description": "成熟期:正常操作", + }, +} + + +class AccountLifecycleManager: + """账号生命周期管理器(单例)""" + + async def register_account( + self, device_id: str, platform: str, register_timestamp: Optional[float] = None + ): + """ + 注册/绑定一个账号。 + device_id+platform 唯一标识。 + register_timestamp: 账号注册时间戳,不传则用当前时间。 + """ + ts = register_timestamp or time.time() + key = f"acct:{device_id}:{platform}" + data = {"register_ts": ts, "platform": platform, "device_id": device_id} + r = await _get_redis() + if r: + try: + import json + await r.set(key, json.dumps(data), ex=86400 * 365) + return + except Exception: + pass + _memory_accounts[key] = data + + async def get_phase(self, device_id: str, platform: str) -> AccountPhase: + key = f"acct:{device_id}:{platform}" + data = None + r = await _get_redis() + if r: + try: + import json + raw = await r.get(key) + if raw: + data = json.loads(raw) + except Exception: + pass + if not data: + data = _memory_accounts.get(key) + if not data: + return AccountPhase.NEW + + age_days = (time.time() - data.get("register_ts", 0)) / 86400 + if age_days <= 7: + return AccountPhase.NEW + elif age_days <= 30: + return AccountPhase.GROWING + return AccountPhase.MATURE + + async def is_new_account(self, device_id: str, platform: str) -> bool: + phase = await self.get_phase(device_id, platform) + return phase == AccountPhase.NEW + + async def get_rules(self, device_id: str, platform: str) -> dict: + phase = await self.get_phase(device_id, platform) + return { + "phase": phase.value, + **PHASE_RULES[phase], + } + + async def check_allowed( + self, device_id: str, platform: str, action: str + ) -> Dict[str, any]: + """ + 检查某操作在当前生命周期是否允许(仅做检查,不消耗配额)。 + 返回 {"allowed": bool, "phase": str, "reason": str} + """ + phase = await self.get_phase(device_id, platform) + rules = PHASE_RULES[phase] + + if phase == AccountPhase.NEW and rules.get("required_warm_up"): + high_risk_actions = { + "batch_add_friend", "batch_send", "send_red_packet", + "transfer", "open_mini_program", + } + if action in high_risk_actions: + return { + "allowed": False, + "phase": phase.value, + "reason": f"新号期禁止高风险操作: {action}", + } + + return {"allowed": True, "phase": phase.value, "reason": ""} + + +account_lifecycle = AccountLifecycleManager() diff --git a/sdk/app/services/anti_ban_alert.py b/sdk/app/services/anti_ban_alert.py new file mode 100644 index 0000000000..1c15fca62d --- /dev/null +++ b/sdk/app/services/anti_ban_alert.py @@ -0,0 +1,133 @@ +""" +防封模块 — 风控告警推送 +当触发日限额耗尽、指纹碰撞、操作时段违规、连续失败等事件时自动推送到飞书群。 +包含冷却机制防止重复告警刷屏。 +""" + +import json +import logging +import time +from typing import Optional + +logger = logging.getLogger(__name__) + +FEISHU_WEBHOOK = "https://open.feishu.cn/open-apis/bot/v2/hook/d0f607da-ae26-43a0-9dbe-2c2c0b90743d" + +ALERT_COOLDOWN = 300 +_last_alert_ts: dict = {} + + +def _can_alert(alert_key: str) -> bool: + now = time.time() + last = _last_alert_ts.get(alert_key, 0) + if now - last < ALERT_COOLDOWN: + return False + _last_alert_ts[alert_key] = now + return True + + +async def send_alert( + level: str, + title: str, + detail: str, + device_id: Optional[str] = None, + platform: Optional[str] = None, +): + """ + 发送风控告警到飞书群。 + level: "warning" | "critical" | "info" + """ + alert_key = f"{level}:{title}:{device_id or 'global'}" + if not _can_alert(alert_key): + logger.debug(f"[告警冷却中] {alert_key}") + return + + icon = {"critical": "🚨", "warning": "⚠️", "info": "ℹ️"}.get(level, "📢") + text = ( + f"{icon}【工作手机·风控告警】\n\n" + f"级别:{level.upper()}\n" + f"事件:{title}\n" + ) + if device_id: + text += f"设备:{device_id}\n" + if platform: + text += f"平台:{platform}\n" + text += f"详情:{detail}\n" + text += f"时间:{time.strftime('%Y-%m-%d %H:%M:%S')}" + + color_map = {"critical": "red", "warning": "orange", "info": "blue"} + card = { + "header": { + "template": color_map.get(level, "blue"), + "title": {"content": f"{icon} 风控告警 · {title}", "tag": "plain_text"}, + }, + "elements": [ + {"tag": "div", "text": {"content": text, "tag": "lark_md"}}, + ], + } + payload = {"msg_type": "interactive", "card": card} + try: + import httpx + async with httpx.AsyncClient(timeout=5) as client: + resp = await client.post(FEISHU_WEBHOOK, json=payload) + body = resp.json() + if body.get("code") != 0: + logger.warning(f"飞书告警发送失败: {body}") + except ImportError: + try: + import urllib.request + data = json.dumps(payload, ensure_ascii=False).encode("utf-8") + req = urllib.request.Request( + FEISHU_WEBHOOK, data=data, + headers={"Content-Type": "application/json; charset=utf-8"}, + ) + urllib.request.urlopen(req, timeout=5) + except Exception as e: + logger.warning(f"飞书告警发送失败(urllib): {e}") + except Exception as e: + logger.warning(f"飞书告警发送异常: {e}") + + +async def alert_daily_limit(device_id: str, platform: str, action: str, count: int, limit: int): + await send_alert( + "warning", + "日操作上限已达", + f"{platform}.{action} 当前 {count}/{limit}", + device_id, platform, + ) + + +async def alert_fingerprint_collision(device_id: str, collided_with: list): + await send_alert( + "critical", + "设备指纹碰撞", + f"与设备 {collided_with} 指纹相同,存在关联封号风险", + device_id, + ) + + +async def alert_outside_hours(device_id: str, action: str): + await send_alert( + "warning", + "非操作时段操作", + f"尝试在禁止时段执行 {action}", + device_id, + ) + + +async def alert_sensitive_content(device_id: str, platform: str, keywords: list): + await send_alert( + "warning", + "敏感内容拦截", + f"检测到敏感词: {keywords[:5]}", + device_id, platform, + ) + + +async def alert_consecutive_failures(device_id: str, platform: str, fail_count: int): + await send_alert( + "critical", + "连续操作失败", + f"连续失败 {fail_count} 次,建议暂停该设备操作", + device_id, platform, + ) diff --git a/sdk/app/services/content_guard.py b/sdk/app/services/content_guard.py new file mode 100644 index 0000000000..fb91b81c28 --- /dev/null +++ b/sdk/app/services/content_guard.py @@ -0,0 +1,149 @@ +""" +防封模块 — 内容防封守卫 +1. 敏感词过滤(黑名单 + 正则模式匹配) +2. 内容差异化(变量替换 + 零宽字符 + 同义词 + 表情随机) +3. 消息唯一性保障 +""" + +import random +import re +import time +import logging +from typing import List, Optional + +logger = logging.getLogger(__name__) + +SENSITIVE_WORDS: List[str] = [ + "加我微信", "微信号", "扫码", "二维码", "转账", "红包", + "免费领", "赚钱", "兼职", "日赚", "月入", "暴利", + "刷单", "代理", "优惠券", "点击链接", "限时", "秒杀", + "抢购", "加群", "进群", "私聊我", "代购", "佣金", + "保证赚", "零风险", "稳赚不赔", "日入过万", "躺赚", + "加Telegram", "加TG", "加WhatsApp", "加WX", + "V信", "威信", "薇信", "WX号", + "银行卡", "信用卡套现", "贷款", "网贷", + "色情", "赌博", "博彩", "棋牌", +] + +SENSITIVE_PATTERNS: List[re.Pattern] = [ + re.compile(r"https?://\S+"), + re.compile(r"www\.\S+"), + re.compile(r"[a-zA-Z0-9]{6,}\.(?:com|cn|net|org|xyz|top|cc|vip)\b"), + re.compile(r"(? str: + result = text + for word, replacement in SAFE_REPLACEMENTS.items(): + result = result.replace(word, replacement) + for word in SENSITIVE_WORDS: + if word in result and word not in SAFE_REPLACEMENTS: + result = result.replace(word, "*" * len(word)) + for pat in SENSITIVE_PATTERNS: + result = pat.sub("[已过滤]", result) + return result + + +def has_sensitive_words(text: str) -> List[str]: + found = [] + for word in SENSITIVE_WORDS: + if word in text: + found.append(word) + for pat in SENSITIVE_PATTERNS: + matches = pat.findall(text) + if matches: + found.extend(matches[:3]) + return found + + +def _get_greeting() -> str: + hour = time.localtime().tm_hour + for hr_range, greetings in GREETINGS_BY_HOUR.items(): + if hour in hr_range: + return random.choice(greetings) + return "您好" + + +def diversify_content( + text: str, + variables: Optional[dict] = None, + add_invisible: bool = True, + add_emoji: bool = False, +) -> str: + """ + 内容差异化处理,确保每条消息唯一。 + + Args: + text: 原始消息文本 + variables: 自定义变量 {"{昵称}": "小明"} 等 + add_invisible: 是否插入零宽字符增加唯一性 + add_emoji: 是否在末尾随机添加表情 + """ + result = text + + result = result.replace("{时间}", _get_greeting()) + result = result.replace("{日期}", time.strftime("%m月%d日")) + + if variables: + for k, v in variables.items(): + result = result.replace(k, str(v)) + + result = filter_sensitive(result) + + if add_invisible and len(result) > 2: + num_inserts = random.randint(1, min(3, len(result) // 4)) + for _ in range(num_inserts): + pos = random.randint(1, len(result) - 1) + char = random.choice(INVISIBLE_CHARS) + result = result[:pos] + char + result[pos:] + + if add_emoji and random.random() < 0.4: + result = result.rstrip() + random.choice(EMOJI_POOL) + + return result + + +def batch_diversify(texts: List[str], **kwargs) -> List[str]: + return [diversify_content(t, **kwargs) for t in texts] diff --git a/sdk/app/services/device_manager.py b/sdk/app/services/device_manager.py index 064d2537c5..a0f8a8b667 100644 --- a/sdk/app/services/device_manager.py +++ b/sdk/app/services/device_manager.py @@ -1,7 +1,8 @@ """ -工作手机SDK v3.0 - 设备管理服务 +工作手机SDK v3.0 - 设备管理服务(含设备指纹校验) """ +import hashlib from motor.motor_asyncio import AsyncIOMotorClient from typing import Optional, List import logging @@ -50,13 +51,17 @@ class DeviceManager: # ========== 设备管理 ========== async def register_device(self, device_data: dict) -> dict: - """注册或更新设备""" - if self.db is None: - return {"device_id": device_data.get("device_id"), "updated": False} - + """注册或更新设备(自动计算并校验指纹)""" device_id = device_data.get("device_id") + + fp_hash = self.compute_fingerprint(device_data) + device_data["fingerprint_hash"] = fp_hash + + if self.db is None: + return {"device_id": device_id, "updated": False, "fingerprint": fp_hash} + device_data["updated_at"] = datetime.now() - + result = await self.db.devices.update_one( {"device_id": device_id}, { @@ -65,8 +70,20 @@ class DeviceManager: }, upsert=True ) - - return {"device_id": device_id, "updated": result.modified_count > 0} + + collision = await self.check_fingerprint_collision(device_id, device_data) + if collision["collision"]: + logger.warning( + f"[防封] 注册设备 {device_id} 指纹碰撞: {collision['collided_with']}" + ) + + return { + "device_id": device_id, + "updated": result.modified_count > 0, + "fingerprint": fp_hash, + "fingerprint_collision": collision["collision"], + "collided_with": collision.get("collided_with", []), + } async def get_device(self, device_id: str) -> Optional[dict]: """获取设备信息""" @@ -101,6 +118,65 @@ class DeviceManager: upsert=True ) + # ========== 设备指纹校验(防封 AF11) ========== + + @staticmethod + def compute_fingerprint(info: dict) -> str: + """从设备上报信息计算指纹哈希(MD5)""" + keys = sorted([ + "brand", "model", "manufacturer", "android_version", + "sdk_version", "serial", "imei", "mac", "bluetooth_mac", + "screen_width", "screen_height", "density", + "cpu_abi", "fingerprint", "android_id", + ]) + parts = [] + for k in keys: + v = info.get(k, "") + if v: + parts.append(f"{k}={v}") + raw = "|".join(parts) + return hashlib.md5(raw.encode("utf-8")).hexdigest() + + async def check_fingerprint_collision(self, device_id: str, info: dict) -> dict: + """ + 检查设备指纹是否与其他设备碰撞。 + 返回 {"collision": bool, "collided_with": [device_ids...]} + """ + fp = self.compute_fingerprint(info) + + if self.db is None: + return {"collision": False, "collided_with": [], "fingerprint": fp} + + try: + await self.db.devices.update_one( + {"device_id": device_id}, + {"$set": {"fingerprint_hash": fp, "fingerprint_info": info}}, + upsert=True, + ) + except Exception as e: + logger.warning(f"指纹写入失败: {e}") + + try: + cursor = self.db.devices.find( + {"fingerprint_hash": fp, "device_id": {"$ne": device_id}}, + {"device_id": 1, "_id": 0}, + ) + collisions = [doc["device_id"] async for doc in cursor] + except Exception as e: + logger.warning(f"指纹碰撞查询失败: {e}") + collisions = [] + + if collisions: + logger.warning( + f"[防封] 设备指纹碰撞! {device_id} 与 {collisions} 指纹相同 (hash={fp})" + ) + + return { + "collision": len(collisions) > 0, + "collided_with": collisions, + "fingerprint": fp, + } + # ========== 命令日志 ========== async def log_command( diff --git a/sdk/app/services/rate_limiter.py b/sdk/app/services/rate_limiter.py new file mode 100644 index 0000000000..d1671052fd --- /dev/null +++ b/sdk/app/services/rate_limiter.py @@ -0,0 +1,284 @@ +""" +防封模块 — 三级频率限制器 +L1 全局限流 / L2 设备限流 / L3 动作限流(按平台+操作类型) + +所有自动化操作必须经过本限流器检查,超频则等待或拒绝。 +依赖 Redis;Redis 不可用时退化为内存字典(重启丢失)。 +""" + +import asyncio +import logging +import random +import time +from typing import Dict, Optional, Tuple + +logger = logging.getLogger(__name__) + +_redis = None +_redis_last_check: float = 0.0 +_REDIS_CHECK_INTERVAL = 60.0 + + +async def _get_redis(): + global _redis, _redis_last_check + now = time.time() + if _redis is not None: + if now - _redis_last_check > _REDIS_CHECK_INTERVAL: + try: + await _redis.ping() + _redis_last_check = now + except Exception: + logger.warning("Redis 连接失效,重置并退化内存模式") + _redis = None + if _redis is not None: + return _redis + try: + import redis.asyncio as aioredis + from config import settings + _redis = aioredis.from_url(settings.REDIS_URL, decode_responses=True) + await _redis.ping() + _redis_last_check = now + return _redis + except Exception as e: + logger.warning(f"Redis 不可用,退化为内存限流: {e}") + _redis = None + return None + + +_memory_store: Dict[str, float] = {} +_memory_daily: Dict[str, int] = {} +_daily_date: str = "" + + +def _today() -> str: + return time.strftime("%Y-%m-%d") + + +PLATFORM_LIMITS: Dict[str, Dict[str, Dict]] = { + "wechat": { + "add_friend": {"interval": (180, 300), "daily_max": 50, "new_daily_max": 10}, + "send_message": {"interval": (30, 60), "daily_max": 200, "new_daily_max": 20}, + "post_moments": {"interval": (7200, 10800), "daily_max": 5, "new_daily_max": 2}, + "batch_send": {"interval": (300, 600), "daily_max": 50, "new_daily_max": 5}, + "like_moments": {"interval": (15, 45), "daily_max": 100, "new_daily_max": 20}, + "comment_moments": {"interval": (60, 180), "daily_max": 50, "new_daily_max": 10}, + "send_group_message": {"interval": (60, 120), "daily_max": 100, "new_daily_max": 15}, + }, + "douyin": { + "send_message": {"interval": (60, 120), "daily_max": 100, "new_daily_max": 15}, + "follow": {"interval": (30, 60), "daily_max": 200, "new_daily_max": 30}, + "like": {"interval": (5, 15), "daily_max": 500, "new_daily_max": 100}, + "comment": {"interval": (60, 180), "daily_max": 50, "new_daily_max": 10}, + }, + "xhs": { + "send_message": {"interval": (60, 180), "daily_max": 50, "new_daily_max": 10}, + "like": {"interval": (15, 30), "daily_max": 300, "new_daily_max": 50}, + "collect": {"interval": (30, 60), "daily_max": 200, "new_daily_max": 30}, + "comment": {"interval": (60, 180), "daily_max": 30, "new_daily_max": 5}, + }, + "xianyu": { + "send_message": {"interval": (60, 120), "daily_max": 80, "new_daily_max": 15}, + }, + "soul": { + "send_message": {"interval": (30, 90), "daily_max": 100, "new_daily_max": 20}, + }, +} + +L1_GLOBAL_RPM = 100 +L2_DEVICE_RPM = 10 + +OPERATION_HOURS = (7, 23) + + +class DailyLimitExceeded(Exception): + """日操作上限已达""" + pass + + +class OutsideOperationHours(Exception): + """非操作时段""" + pass + + +class RateLimitWaiting(Exception): + """需要等待(信息性,不应到达调用方)""" + pass + + +class AntiDetectRateLimiter: + """三级防封限流器(单例)""" + + def get_limit(self, platform: str, action: str) -> Optional[Dict]: + plat = PLATFORM_LIMITS.get(platform) + if not plat: + return None + for key in (action, action.replace("-", "_")): + if key in plat: + return plat[key] + return None + + def check_operation_hours(self) -> bool: + """检查当前时间是否在允许操作时段内(默认 7:00-23:00)""" + import datetime + hour = datetime.datetime.now().hour + return OPERATION_HOURS[0] <= hour < OPERATION_HOURS[1] + + async def _check_l1_global(self) -> bool: + """L1 全局限流:所有设备总请求 <= L1_GLOBAL_RPM/分钟""" + now = time.time() + window_key = f"rl:l1:global:{int(now // 60)}" + r = await _get_redis() + if r: + try: + count = await r.incr(window_key) + if count == 1: + await r.expire(window_key, 120) + if count > L1_GLOBAL_RPM: + logger.warning(f"[RateLimiter] L1 全局限流触发: {count}/{L1_GLOBAL_RPM} RPM") + return False + return True + except Exception: + pass + win_key = f"l1:{int(now // 60)}" + _memory_daily[win_key] = _memory_daily.get(win_key, 0) + 1 + return _memory_daily[win_key] <= L1_GLOBAL_RPM + + async def _check_l2_device(self, device_id: str) -> bool: + """L2 设备限流:单设备总请求 <= L2_DEVICE_RPM/分钟""" + now = time.time() + window_key = f"rl:l2:{device_id}:{int(now // 60)}" + r = await _get_redis() + if r: + try: + count = await r.incr(window_key) + if count == 1: + await r.expire(window_key, 120) + if count > L2_DEVICE_RPM: + logger.warning(f"[RateLimiter] L2 设备限流触发 {device_id}: {count}/{L2_DEVICE_RPM} RPM") + return False + return True + except Exception: + pass + win_key = f"l2:{device_id}:{int(now // 60)}" + _memory_daily[win_key] = _memory_daily.get(win_key, 0) + 1 + return _memory_daily[win_key] <= L2_DEVICE_RPM + + async def check_and_wait( + self, + device_id: str, + platform: str, + action: str, + is_new_account: bool = False, + ) -> float: + """ + 三级检查:L1全局 → L2设备 → L3动作 + 时段检查。 + 返回实际等待的秒数。 + 若日上限已到,抛 DailyLimitExceeded。 + """ + if not self.check_operation_hours(): + raise OutsideOperationHours( + f"当前不在操作时段 ({OPERATION_HOURS[0]}:00-{OPERATION_HOURS[1]}:00)" + ) + + if not await self._check_l1_global(): + wait = random.uniform(3.0, 8.0) + logger.info(f"[RateLimiter] L1 全局超限,等待 {wait:.1f}s") + await asyncio.sleep(wait) + + if not await self._check_l2_device(device_id): + wait = random.uniform(5.0, 15.0) + logger.info(f"[RateLimiter] L2 设备超限 {device_id},等待 {wait:.1f}s") + await asyncio.sleep(wait) + + limit = self.get_limit(platform, action) + if limit is None: + await asyncio.sleep(random.uniform(0.5, 2.0)) + return 0.0 + + daily_max = limit.get("new_daily_max" if is_new_account else "daily_max", 9999) + daily_count = await self._get_daily_count(device_id, platform, action) + if daily_count >= daily_max: + raise DailyLimitExceeded( + f"{platform}.{action} 已达日上限 {daily_max}(当前 {daily_count})" + ) + + interval_range: Tuple[float, float] = limit["interval"] + min_wait = random.uniform(*interval_range) + + last_ts = await self._get_last_ts(device_id, platform, action) + elapsed = time.time() - last_ts if last_ts else float("inf") + wait = max(0.0, min_wait - elapsed) + + if wait > 0: + jitter = random.uniform(-wait * 0.1, wait * 0.15) + wait = max(0.3, wait + jitter) + logger.info( + f"[RateLimiter] {device_id}/{platform}.{action} 等待 {wait:.1f}s " + f"(interval={interval_range}, daily={daily_count}/{daily_max})" + ) + await asyncio.sleep(wait) + + await self._record(device_id, platform, action) + return wait + + async def get_daily_count(self, device_id: str, platform: str, action: str) -> int: + return await self._get_daily_count(device_id, platform, action) + + def get_platform_config(self, platform: str, action: str) -> Optional[Dict]: + """获取某平台某动作的限流配置(供风控看板展示)""" + return self.get_limit(platform, action) + + # ---- 存储层 ---- + + async def _get_last_ts(self, device_id: str, platform: str, action: str) -> float: + key = f"rl:ts:{device_id}:{platform}:{action}" + r = await _get_redis() + if r: + try: + val = await r.get(key) + return float(val) if val else 0.0 + except Exception: + pass + return _memory_store.get(key, 0.0) + + async def _get_daily_count(self, device_id: str, platform: str, action: str) -> int: + global _daily_date + today = _today() + key = f"rl:daily:{today}:{device_id}:{platform}:{action}" + r = await _get_redis() + if r: + try: + val = await r.get(key) + return int(val) if val else 0 + except Exception: + pass + if _daily_date != today: + _memory_daily.clear() + _daily_date = today + return _memory_daily.get(key, 0) + + async def _record(self, device_id: str, platform: str, action: str): + now = time.time() + today = _today() + ts_key = f"rl:ts:{device_id}:{platform}:{action}" + daily_key = f"rl:daily:{today}:{device_id}:{platform}:{action}" + r = await _get_redis() + if r: + try: + pipe = r.pipeline() + pipe.set(ts_key, str(now), ex=86400) + pipe.incr(daily_key) + pipe.expire(daily_key, 86400) + await pipe.execute() + return + except Exception: + pass + _memory_store[ts_key] = now + global _daily_date + if _daily_date != today: + _memory_daily.clear() + _daily_date = today + _memory_daily[daily_key] = _memory_daily.get(daily_key, 0) + 1 + + +rate_limiter = AntiDetectRateLimiter() diff --git a/sdk/app/services/ws_hub.py b/sdk/app/services/ws_hub.py index 662572a788..81afe59cb0 100644 --- a/sdk/app/services/ws_hub.py +++ b/sdk/app/services/ws_hub.py @@ -129,6 +129,18 @@ class WebSocketHub: elif msg_type == "heartbeat": if device_id in self.device_info: self.device_info[device_id]["last_heartbeat"] = datetime.now().isoformat() + hb_status = data.get("status") or {} + if hb_status: + self.device_info[device_id]["quick_status"] = hb_status + if hb_status.get("wechat_running") is not None: + self.device_info[device_id]["wechat_running"] = hb_status["wechat_running"] + self.device_info[device_id]["wechat_foreground"] = hb_status.get("wechat_foreground", False) + if hb_status.get("network_type"): + self.device_info[device_id]["network_type"] = hb_status["network_type"] + if hb_status.get("screen_on") is not None: + self.device_info[device_id]["screen_on"] = hb_status["screen_on"] + if hb_status.get("current_app"): + self.device_info[device_id]["current_app"] = hb_status["current_app"] try: dm = _get_device_manager() await dm.update_heartbeat(device_id) diff --git a/sdk/data/nav_cache/nav_v8.0.56_1080x2400.json b/sdk/data/nav_cache/nav_v8.0.56_1080x2400.json index cfb7ae9ec2..34172af3b8 100644 --- a/sdk/data/nav_cache/nav_v8.0.56_1080x2400.json +++ b/sdk/data/nav_cache/nav_v8.0.56_1080x2400.json @@ -1,7 +1,7 @@ { "version": "8.0.56", "screen": "1080x2400", - "updated": "2026-03-14 16:39:18", + "updated": "2026-03-14 17:57:15", "pages": { "me": { "recorded_at": "2026-03-14 16:32", @@ -323,9 +323,9 @@ } ], "target_page": "account_security", - "success_count": 0, + "success_count": 1, "fail_count": 0, - "last_success": null + "last_success": "2026-03-14 17:57:15" }, "to_safety_center": { "name": "to_safety_center", diff --git a/sdk/data/operation_logs/ops_20260314.jsonl b/sdk/data/operation_logs/ops_20260314.jsonl new file mode 100644 index 0000000000..ec83155e5b --- /dev/null +++ b/sdk/data/operation_logs/ops_20260314.jsonl @@ -0,0 +1,5 @@ +{"action": "send_message", "params": {"to_id": "阿猫", "content": "你好阿猫", "msg_type": "text"}, "device": "xgfe65eimrrofyws", "started_at": "2026-03-14 17:56:03", "steps": [], "success": true, "duration_ms": 24480, "ended_at": "2026-03-14 17:56:27", "result_summary": "{'success': True, 'mode': 'adb', 'timestamp': 1773482187961, 'message_id': 'adb_1773482187961'}"} +{"action": "check_restrictions", "params": {}, "device": "xgfe65eimrrofyws", "started_at": "2026-03-14 17:57:01", "steps": [{"type": "tap_tab", "detail": "微信", "coords": [135, 2148], "success": true, "ts": 3.15}], "success": true, "duration_ms": 4025, "ended_at": "2026-03-14 17:57:05", "result_summary": "{'success': True, 'mode': 'adb', 'timestamp': 1773482225487, 'restricted': False, 'restrictions': [], 'note': '未检测到功能限制'}"} +{"action": "get_profile", "params": {}, "device": "xgfe65eimrrofyws", "started_at": "2026-03-14 17:57:00", "steps": [{"type": "tap_tab", "detail": "我", "coords": [945, 2148], "success": true, "ts": 3.18}], "success": true, "duration_ms": 7088, "ended_at": "2026-03-14 17:57:07", "result_summary": "{'success': True, 'mode': 'adb', 'timestamp': 1773482227259, 'profile': {'nickname': 'BL-卡若私域%1.0 | NT11|45'}}"} +{"action": "check_account_status", "params": {}, "device": "xgfe65eimrrofyws", "started_at": "2026-03-14 17:57:00", "steps": [{"type": "tap_tab", "detail": "我", "coords": [945, 2148], "success": true, "ts": 3.68}, {"type": "tap_tab", "detail": "我", "coords": [945, 2148], "success": true, "ts": 5.19}, {"type": "scroll_up", "detail": "", "coords": null, "success": true, "ts": 6.7}, {"type": "tap_text", "detail": "设置", "coords": [197, 1676], "success": true, "ts": 9.09}, {"type": "scroll_to_top", "detail": "", "coords": null, "success": true, "ts": 12.56}, {"type": "tap_text", "detail": "账号与安全", "coords": [250, 300], "success": true, "ts": 14.96}], "success": true, "duration_ms": 18596, "ended_at": "2026-03-14 17:57:19", "result_summary": "{'success': True, 'mode': 'adb', 'timestamp': 1773482239343, 'status': {'account_active': True}}"} +{"action": "get_contacts", "params": {"limit": 5}, "device": "xgfe65eimrrofyws", "started_at": "2026-03-14 17:57:22", "steps": [{"type": "tap_tab", "detail": "通讯录", "coords": [405, 2148], "success": true, "ts": 5.26}], "success": true, "duration_ms": 6260, "ended_at": "2026-03-14 17:57:29", "result_summary": "{'success': True, 'mode': 'adb', 'timestamp': 1773482249186, 'contacts': [], 'count': 0}"} diff --git a/开发文档/10、项目管理/工作日志.md b/开发文档/10、项目管理/工作日志.md index 1aced4c9aa..3e13c37a22 100644 --- a/开发文档/10、项目管理/工作日志.md +++ b/开发文档/10、项目管理/工作日志.md @@ -5,6 +5,40 @@ --- +## 2026-03-15 | 防封模块深度验证+13项BUG修复 + +### 执行人: 火炬(机擎全员) + +### 验证方式 +- 代码深度审计(9个文件,5个维度) +- 防封策略文档 vs 代码逐章差距分析(55项需求对照) +- 全网搜索最新微信2025-2026风控规则+Frida反检测技术 + +### 发现问题(审计前覆盖率仅40%) +- **3个P0崩溃BUG**: unified.py get_phase缺await+缺参数、L1/L2限流仅定义常量未实现、80%端点绕过防封守卫 +- **7个P1缺陷**: 默认账号阶段MATURE(应为NEW)、Redis断线永不恢复、贝塞尔滑动死代码、Frida端口模块共享、无操作时段控制、无告警推送、敏感词库不足 +- **多项缺失**: 自然行为链、正则匹配、内容格式校验 + +### 修复内容(13项,全部完成) +1. [P0] unified.py: get_phase/get_rules 补 await + platform 参数 +2. [P0] rate_limiter: 实现 L1 全局限流(100RPM) + L2 设备限流(10RPM) 滑动窗口 +3. [P0] rate_limiter: 新增操作时段控制(7:00-23:00),OutsideOperationHours 异常 +4. [P0] unified.py: 补齐6个端点防封守卫(comment/reply, batch-add, group/send, moments/like, moments/comment, mass-send) +5. [P0] unified.py: guard返回字段统一为"reason" +6. [P1] account_lifecycle: 未注册账号默认 NEW→最严格(原MATURE→最宽松) +7. [P1] account_lifecycle: Redis set 加 TTL(1年) +8. [P1] rate_limiter + account_lifecycle: Redis 健康检查(60s间隔ping),断线自动恢复 +9. [P1] base.py: 贝塞尔滑动 swipe_points 替代直线 swipe(降级兼容) +10. [P1] frida_manager: 端口从模块级改为实例级(_random_port()) +11. [P1] 新增 anti_ban_alert.py 风控告警(5种告警:日限额/指纹碰撞/时段违规/敏感内容/连续失败) +12. [P1] content_guard: 扩充敏感词40+,新增7个正则模式(URL/手机号/邮箱/微信号/QQ号) +13. [P1] base.py + wechat/skill.py: 新增 natural_behavior_before/after 自然行为链 + +### 进度: Phase 5 覆盖率 40% → 70%+ | 整体项目 95% +### 下一步: Phase 3B 独立管理端 或 Phase 4 部署上线 + +--- + ## 2026-03-14 | 第8次对话 — 微信全功能v2 + 导航缓存系统 ### 完成内容 diff --git a/开发文档/2、架构/工作手机SDKv3_系统架构图.png b/开发文档/2、架构/工作手机SDKv3_系统架构图.png new file mode 100644 index 0000000000..9d4191b861 Binary files /dev/null and b/开发文档/2、架构/工作手机SDKv3_系统架构图.png differ diff --git a/开发文档/2、架构/工作手机SDK架构图.png b/开发文档/2、架构/工作手机SDK架构图.png new file mode 100644 index 0000000000..c11de0984b Binary files /dev/null and b/开发文档/2、架构/工作手机SDK架构图.png differ diff --git a/开发文档/2、架构/工作手机微信控制系统架构图.png b/开发文档/2、架构/工作手机微信控制系统架构图.png new file mode 100644 index 0000000000..64d76b05fa Binary files /dev/null and b/开发文档/2、架构/工作手机微信控制系统架构图.png differ diff --git a/开发文档/2、架构/工作手机设备端运转逻辑.png b/开发文档/2、架构/工作手机设备端运转逻辑.png new file mode 100644 index 0000000000..2a62740343 Binary files /dev/null and b/开发文档/2、架构/工作手机设备端运转逻辑.png differ diff --git a/开发文档/2、架构/微信控制-设备服务器交互图.html b/开发文档/2、架构/微信控制-设备服务器交互图.html new file mode 100644 index 0000000000..902559df44 --- /dev/null +++ b/开发文档/2、架构/微信控制-设备服务器交互图.html @@ -0,0 +1,115 @@ + + +
+ +架构图风格标准 · 扁平化 · 蓝绿配色 · 开发文档/2、架构/
+ + diff --git a/开发文档/2、架构/架构图风格标准.md b/开发文档/2、架构/架构图风格标准.md new file mode 100644 index 0000000000..f15d91d726 --- /dev/null +++ b/开发文档/2、架构/架构图风格标准.md @@ -0,0 +1,45 @@ +# 工作手机 · 架构图风格标准 + +> **用途**:后续所有架构图、流程图、应用示意均按此风格生成,保持统一视觉。 + +--- + +## 一、风格要点 + +| 维度 | 标准 | +|------|------| +| **语言** | 中文 | +| **风格** | 扁平化、科技感、现代 UI | +| **主色** | 蓝色、绿色、浅灰 | +| **形状** | 圆角矩形、清晰层次 | +| **箭头** | 实线箭头表示数据/控制流向 | +| **字体** | 清晰可读、层级分明 | + +--- + +## 二、分层与布局 + +- **自上而下**:业务层 → 网关层 → 服务层 → 数据层 → 设备层 +- **同层并排**:同级模块水平排列(如设备A/B/C、MongoDB/Redis/MinIO) +- **连接线**:标明协议(HTTPS、WebSocket)与端口 + +--- + +## 三、模块标注规范 + +- **服务/组件**:方框内写中文名称 + 英文缩写(如「设备管理 DeviceSvc」) +- **设备**:标注 设备A / 设备B / 设备C,可加地域(厦门/北京/上海) +- **数据流**:箭头旁标注「发消息」「心跳」「execute」等动作 + +--- + +## 四、参考图 + +| 图名 | 路径 | 说明 | +|------|------|------| +| 系统架构图 | 工作手机SDK架构图.png | 整体四层架构 | +| 设备-服务器交互 | 设备与服务器交互流程_微信控制.png | 设备↔服务器、微信控制完整链路 | + +--- + +*后续生成架构图时,按以上标准执行。* diff --git a/开发文档/2、架构/系统架构.md b/开发文档/2、架构/系统架构.md index 13b23534fd..edf0d2b486 100644 --- a/开发文档/2、架构/系统架构.md +++ b/开发文档/2、架构/系统架构.md @@ -7,7 +7,10 @@ ## 一、整体架构图(AI+Skill版) -> **图形化架构图**:见 [工作手机SDK架构图.png](工作手机SDK架构图.png),可直接打开查看。 +> **图形化架构图**: +> - 整体架构:[工作手机SDK架构图.png](工作手机SDK架构图.png) +> - 微信控制·设备服务器交互:[微信控制-设备服务器交互图.html](微信控制-设备服务器交互图.html)(浏览器打开) +> - 风格标准:[架构图风格标准.md](架构图风格标准.md)(以后生成架构图按此风格) ``` ┌─────────────────────────────────────────────────────────────────────────────┐ @@ -268,6 +271,9 @@ class CaptureService: ## 四、数据流设计 +> **图形化流程图**:设备与服务器交互(微信控制)见 [设备与服务器交互流程_微信控制.png](设备与服务器交互流程_微信控制.png)。 +> **架构图风格标准**:见 [架构图风格标准.md](架构图风格标准.md),后续架构图均按此风格生成。 + ### 4.1 API请求流程 ```mermaid diff --git a/开发文档/2、架构/设备与服务器交互流程_微信控制.png b/开发文档/2、架构/设备与服务器交互流程_微信控制.png new file mode 100644 index 0000000000..6ccb55214c Binary files /dev/null and b/开发文档/2、架构/设备与服务器交互流程_微信控制.png differ diff --git a/开发文档/2、架构/防封服务端核心能力拆细图.png b/开发文档/2、架构/防封服务端核心能力拆细图.png new file mode 100644 index 0000000000..7ff48a5b60 Binary files /dev/null and b/开发文档/2、架构/防封服务端核心能力拆细图.png differ diff --git a/开发文档/2、架构/防封模块架构图.html b/开发文档/2、架构/防封模块架构图.html new file mode 100644 index 0000000000..d77fb51df2 --- /dev/null +++ b/开发文档/2、架构/防封模块架构图.html @@ -0,0 +1,89 @@ + + + + +WorkPhone SDK v3.0 Architecture
+ FastAPI :8899 · WeChat 8.0.56 · 98 Endpoints +