feat: 防封模块、架构图、防封策略、account_lifecycle/anti_ban_alert/content_guard/rate_limiter

Made-with: Cursor
This commit is contained in:
卡若
2026-03-14 18:01:27 +08:00
parent 01f6742a33
commit df7999a024
31 changed files with 2594 additions and 30 deletions

2
.gitignore vendored
View File

@@ -23,6 +23,8 @@ sdk/android-app/build/
# >20MB 大文件GitHub/Gitea 限制)
开发文档/6、后端/github-repos/
sdk/apks/*.apk
apk_analysis/
sdk/admin/
# 敏感配置
sdk/.env

View File

@@ -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)

View File

@@ -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

View File

@@ -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 看到"""

View File

@@ -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)

View File

@@ -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,

View File

@@ -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()

View File

@@ -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,
)

View File

@@ -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"(?<!\d)1[3-9]\d{9}(?!\d)"),
re.compile(r"[\w.+-]+@[\w-]+\.[\w.-]+"),
re.compile(r"(?:微信|wx|WX|vx|VX)[号:\s]*[a-zA-Z0-9_]{5,20}"),
re.compile(r"(?:QQ|qq)[号:\s]*\d{5,12}"),
]
SAFE_REPLACEMENTS = {
"加我微信": "联系我",
"微信号": "联系方式",
"扫码": "查看",
"二维码": "图片",
"免费领": "获取",
"赚钱": "收益",
"兼职": "合作",
"优惠券": "福利",
"点击链接": "查看详情",
"限时": "近期",
"秒杀": "特价",
"抢购": "选购",
"V信": "联系方式",
"威信": "联系方式",
"薇信": "联系方式",
}
GREETINGS_BY_HOUR = {
range(6, 9): ["早上好", "早安", "上午好"],
range(9, 12): ["上午好", "您好"],
range(12, 14): ["中午好", "午安"],
range(14, 18): ["下午好", "您好"],
range(18, 22): ["晚上好", "您好"],
range(22, 24): ["晚安", "您好"],
range(0, 6): ["您好"],
}
EMOJI_POOL = [
"😊", "👍", "🙏", "", "💯", "🎉", "😄", "🤝",
"💪", "🌟", "👏", "😁", "🙂", "❤️", "🔥", "💐",
]
ZWS = "\u200b" # 零宽空格
ZWNJ = "\u200c" # 零宽非连接符
ZWJ = "\u200d" # 零宽连接符
INVISIBLE_CHARS = [ZWS, ZWNJ, ZWJ]
def filter_sensitive(text: str) -> 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]

View File

@@ -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(

View File

@@ -0,0 +1,284 @@
"""
防封模块 — 三级频率限制器
L1 全局限流 / L2 设备限流 / L3 动作限流(按平台+操作类型)
所有自动化操作必须经过本限流器检查,超频则等待或拒绝。
依赖 RedisRedis 不可用时退化为内存字典(重启丢失)。
"""
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()

View File

@@ -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)

View File

@@ -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",

View File

@@ -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}"}

View File

@@ -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 + 导航缓存系统
### 完成内容

Binary file not shown.

After

Width:  |  Height:  |  Size: 933 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -0,0 +1,115 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>工作手机 · 微信控制 · 设备与服务器交互流程图</title>
<style>
* { box-sizing: border-box; }
body {
font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f5f7fa;
padding: 40px;
margin: 0;
}
h1 {
text-align: center;
color: #1a365d;
font-size: 24px;
margin-bottom: 32px;
}
.flow {
max-width: 900px;
margin: 0 auto;
}
.box {
border-radius: 12px;
padding: 20px 24px;
margin: 16px 0;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.box-1 { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #fff; }
.box-2 { background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); color: #fff; }
.box-3 { background: #fff; border: 2px solid #e2e8f0; }
.arrow {
text-align: center;
color: #64748b;
font-size: 14px;
padding: 8px 0;
}
.devices {
display: flex;
gap: 24px;
justify-content: center;
flex-wrap: wrap;
margin: 24px 0;
}
.device {
width: 200px;
background: #fff;
border-radius: 12px;
padding: 20px;
border: 2px solid #e2e8f0;
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
}
.device-title {
font-weight: bold;
color: #1e293b;
margin-bottom: 12px;
font-size: 16px;
}
.device-item {
font-size: 13px;
color: #475569;
margin: 6px 0;
}
.note {
text-align: center;
color: #94a3b8;
font-size: 12px;
margin-top: 32px;
}
</style>
</head>
<body>
<h1>工作手机 · 微信控制 · 设备与服务器交互流程图</h1>
<div class="flow">
<div class="box box-1">
<strong>1) 存客宝后端</strong><br>
WorkPhoneSDK::sendMessage('wechat', to_id, content)
</div>
<div class="arrow">↓ HTTPS POST /api/v3/message/send</div>
<div class="box box-2">
<strong>2) 工作手机SDK服务器</strong><br>
unified 路由 → 通道选择 → 组包 { script: wechat, action: send_message, params }<br>
WebSocket Hub 下发 execute
</div>
<div class="arrow">↓ WebSocket execute</div>
<div class="devices">
<div class="device">
<div class="device-title">设备 A</div>
<div class="device-item">工作手机 Agent APP</div>
<div class="device-item">WeChat Skill</div>
<div class="device-item">uiautomator2 操作微信</div>
</div>
<div class="device">
<div class="device-title">设备 B</div>
<div class="device-item">工作手机 Agent APP</div>
<div class="device-item">WeChat Skill</div>
<div class="device-item">uiautomator2 操作微信</div>
</div>
<div class="device">
<div class="device-title">设备 C</div>
<div class="device-item">工作手机 Agent APP</div>
<div class="device-item">WeChat Skill</div>
<div class="device-item">uiautomator2 操作微信</div>
</div>
</div>
<div class="arrow">↑ response { success, data }</div>
<div class="box box-3">
<strong>3) 返回存客宝</strong><br>
服务器收到设备 response → 返回给存客宝
</div>
</div>
<p class="note">架构图风格标准 · 扁平化 · 蓝绿配色 · 开发文档/2、架构/</p>
</body>
</html>

View File

@@ -0,0 +1,45 @@
# 工作手机 · 架构图风格标准
> **用途**:后续所有架构图、流程图、应用示意均按此风格生成,保持统一视觉。
---
## 一、风格要点
| 维度 | 标准 |
|------|------|
| **语言** | 中文 |
| **风格** | 扁平化、科技感、现代 UI |
| **主色** | 蓝色、绿色、浅灰 |
| **形状** | 圆角矩形、清晰层次 |
| **箭头** | 实线箭头表示数据/控制流向 |
| **字体** | 清晰可读、层级分明 |
---
## 二、分层与布局
- **自上而下**:业务层 → 网关层 → 服务层 → 数据层 → 设备层
- **同层并排**同级模块水平排列如设备A/B/C、MongoDB/Redis/MinIO
- **连接线**标明协议HTTPS、WebSocket与端口
---
## 三、模块标注规范
- **服务/组件**:方框内写中文名称 + 英文缩写(如「设备管理 DeviceSvc」
- **设备**:标注 设备A / 设备B / 设备C可加地域厦门/北京/上海)
- **数据流**箭头旁标注「发消息」「心跳」「execute」等动作
---
## 四、参考图
| 图名 | 路径 | 说明 |
|------|------|------|
| 系统架构图 | 工作手机SDK架构图.png | 整体四层架构 |
| 设备-服务器交互 | 设备与服务器交互流程_微信控制.png | 设备↔服务器、微信控制完整链路 |
---
*后续生成架构图时,按以上标准执行。*

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -0,0 +1,89 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>防封模块架构图</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, "PingFang SC", sans-serif; background: #f5f7fa; padding: 32px; }
.title { text-align: center; font-size: 22px; color: #1a365d; margin-bottom: 28px; font-weight: 600; }
.wrap { max-width: 950px; margin: 0 auto; }
.arrow-label { text-align: center; font-size: 13px; color: #64748b; padding: 6px 0; }
.arrow-red { color: #dc2626; font-weight: 500; }
.layer { margin-bottom: 16px; }
.row { display: flex; justify-content: center; gap: 12px; flex-wrap: wrap; }
.box {
border-radius: 10px; padding: 14px 20px; text-align: center;
box-shadow: 0 1px 4px rgba(0,0,0,.08);
}
.box-top { background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%); color: #fff; font-size: 14px; }
.box-sdk { background: linear-gradient(135deg, #0d9488 0%, #059669 100%); color: #fff; padding: 16px 28px; font-size: 15px; }
.server-wrap {
border: 2px solid #cbd5e1; border-radius: 12px; padding: 20px; margin: 12px 0;
background: #f8fafc;
}
.server-title { font-size: 14px; color: #475569; margin-bottom: 14px; font-weight: 600; }
.box-inner { background: linear-gradient(135deg, #5eead4 0%, #2dd4bf 100%); color: #0f766e; padding: 12px 18px; font-size: 13px; border-radius: 8px; }
.box-inner2 { background: linear-gradient(135deg, #67e8f9 0%, #22d3ee 100%); color: #0e7490; padding: 12px 18px; font-size: 13px; border-radius: 8px; }
.phone-wrap { border: 2px solid #cbd5e1; border-radius: 12px; padding: 16px; display: inline-block; margin: 0 8px; background: #fff; }
.phone-title { font-size: 13px; color: #475569; margin-bottom: 10px; font-weight: 600; }
.flow-h { display: flex; align-items: center; justify-content: center; gap: 8px; flex-wrap: wrap; margin: 10px 0; }
</style>
</head>
<body>
<div class="title">防封模块 · 四层架构</div>
<div class="wrap">
<div class="row layer">
<div class="box box-top">存客宝 / 业务方</div>
</div>
<div class="arrow-label">sendMessage / 请求</div>
<div class="row layer">
<div class="box box-sdk">统一 API 接口</div>
</div>
<div class="arrow-label">HTTPS REST API</div>
<div class="server-wrap">
<div class="server-title">防封服务端</div>
<div class="flow-h">
<div class="box box-inner">鉴权</div>
<span></span>
<div class="box box-inner">防封守卫</div>
<span></span>
<div class="box box-inner">通道选择</div>
<span></span>
<div class="box box-inner">执行</div>
</div>
<div class="arrow-label" style="margin-top:12px">防封守卫</div>
<div class="flow-h">
<div class="box box-inner2">rate_limiter<br>三级限流</div>
<div class="box box-inner2">content_guard<br>内容过滤</div>
<div class="box box-inner2">account_lifecycle<br>账号生命周期</div>
<div class="box box-inner2">device_manager<br>指纹碰撞</div>
<div class="box box-inner2">anti_ban_alert<br>风控告警</div>
</div>
</div>
<div class="arrow-label arrow-red">WebSocket execute · 指令下发</div>
<div class="row layer">
<div class="phone-wrap">
<div class="phone-title">工作手机 · 设备端防封</div>
<div class="box box-inner" style="margin:4px 0">Agent</div>
<div class="arrow-label" style="font-size:11px">设备指纹 / Root隐藏 / Frida反检测 / U2拟人化</div>
</div>
<div class="phone-wrap">
<div class="phone-title">应用层 Skill</div>
<div class="box box-inner2" style="margin:4px 0">微信 抖音 小红书 闲鱼</div>
</div>
<div class="phone-wrap">
<div class="phone-title">行为层防封</div>
<div class="box box-inner2" style="margin:4px 0">human_delay · human_type · 贝塞尔轨迹</div>
</div>
<div class="phone-wrap">
<div class="phone-title">微信APP</div>
<div class="box box-inner" style="margin:4px 0">uiautomator2</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,43 @@
flowchart TB
subgraph 核心公式
F[防封 = 设备隔离 × 环境伪装 × 行为拟人 × 频率控制 × 内容差异 × 监控应急]
end
subgraph 一_设备端["一、设备端防封 (Agent)"]
D1[设备指纹隔离<br/>一机一号一卡一IP]
D2[Root 隐藏<br/>Magisk + Shamiko + HMA]
D3[Frida 反检测<br/>Phantom-Frida]
D4[U2 拟人化<br/>随机延迟/轨迹]
end
subgraph 二_服务端["二、服务端防封"]
REQ[请求进入 → 鉴权]
GUARD[防封守卫]
REQ --> GUARD
GUARD --> R1[rate_limiter 三级限流]
GUARD --> R2[content_guard 内容过滤]
GUARD --> R3[account_lifecycle 账号生命周期]
GUARD --> R4[device_manager 指纹碰撞]
GUARD --> R5[anti_ban_alert 风控告警]
R1 --> EXEC[通道路由 → 执行]
R2 --> EXEC
R3 --> EXEC
end
subgraph 三_应用层["三、应用层防封 (Skill)"]
A1[微信]
A2[抖音]
A3[小红书]
A4[闲鱼]
end
subgraph 四_行为层["四、行为层防封"]
B1[human_delay 拟人延迟]
B2[human_type 逐字输入]
B3[贝塞尔轨迹滑动]
end
F --> 一_设备端
一_设备端 --> 二_服务端
二_服务端 --> 三_应用层
三_应用层 --> 四_行为层

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -0,0 +1,657 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>机擎SDK · API架构与交互流程</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
<style>
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
:root{
--bg:#0a1628;--bg2:#0d1f3c;--bg3:#132744;
--cyan:#00d4ff;--green:#00ff88;--orange:#ff6b35;--gold:#ffd700;
--purple:#a855f7;--red:#ff4757;--blue:#3b82f6;--pink:#ec4899;
--text:#e2e8f0;--text2:#94a3b8;--text3:#64748b;
--card:#0f1f38;--border:rgba(0,212,255,.12);
}
html{scroll-behavior:smooth}
body{
font-family:'Noto Sans SC',system-ui,sans-serif;
background:var(--bg);color:var(--text);
line-height:1.6;overflow-x:hidden;
}
/* ═══ scrollbar ═══ */
::-webkit-scrollbar{width:6px}
::-webkit-scrollbar-track{background:var(--bg)}
::-webkit-scrollbar-thumb{background:var(--cyan);border-radius:3px}
/* ═══ canvas bg ═══ */
#bg-canvas{position:fixed;inset:0;z-index:0;pointer-events:none;opacity:.35}
/* ═══ layout ═══ */
.wrapper{position:relative;z-index:1;max-width:1440px;margin:0 auto;padding:0 32px 80px}
/* ═══ header ═══ */
.hero{text-align:center;padding:72px 0 48px}
.hero h1{
font-family:'Orbitron',monospace;font-weight:900;
font-size:clamp(28px,5vw,52px);
background:linear-gradient(135deg,var(--cyan),var(--green));
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
letter-spacing:2px;
}
.hero .sub{
font-size:15px;color:var(--text2);margin-top:12px;letter-spacing:4px;
font-family:'Orbitron',monospace;text-transform:uppercase;
}
.hero .version{
display:inline-block;margin-top:16px;
padding:4px 16px;border-radius:20px;font-size:12px;
border:1px solid var(--cyan);color:var(--cyan);
font-family:'Orbitron',monospace;
}
/* ═══ section ═══ */
.section{margin-top:64px}
.section-title{
font-family:'Orbitron',monospace;font-weight:700;
font-size:clamp(18px,3vw,26px);color:var(--cyan);
display:flex;align-items:center;gap:12px;
margin-bottom:32px;
}
.section-title::before{
content:'';width:4px;height:28px;border-radius:2px;
background:linear-gradient(180deg,var(--cyan),var(--green));
}
/* ═══ 区块1: 架构总览 ═══ */
.arch-grid{
display:grid;
grid-template-columns:1fr auto 1.3fr auto 1fr;
align-items:stretch;gap:0;
}
.arch-box{
background:var(--card);border:1px solid var(--border);
border-radius:16px;padding:28px 24px;
position:relative;overflow:hidden;
}
.arch-box::before{
content:'';position:absolute;top:0;left:0;right:0;height:3px;
border-radius:16px 16px 0 0;
}
.arch-box.caller::before{background:linear-gradient(90deg,var(--green),var(--cyan))}
.arch-box.engine::before{background:linear-gradient(90deg,var(--cyan),var(--purple))}
.arch-box.device::before{background:linear-gradient(90deg,var(--orange),var(--gold))}
.arch-box .box-icon{font-size:36px;margin-bottom:12px}
.arch-box h3{
font-family:'Orbitron',monospace;font-size:15px;
color:var(--cyan);margin-bottom:16px;letter-spacing:1px;
}
.arch-box.engine h3{color:var(--purple)}
.arch-box.device h3{color:var(--orange)}
.arch-box ul{list-style:none;display:flex;flex-direction:column;gap:8px}
.arch-box li{
font-size:13px;color:var(--text2);
padding:8px 12px;border-radius:8px;
background:rgba(255,255,255,.03);
border:1px solid rgba(255,255,255,.04);
display:flex;align-items:center;gap:8px;
}
.arch-box li .dot{
width:6px;height:6px;border-radius:50%;flex-shrink:0;
}
.arch-box.caller li .dot{background:var(--green)}
.arch-box.engine li .dot{background:var(--cyan)}
.arch-box.device li .dot{background:var(--orange)}
.arch-box .engine-core{
margin-top:12px;padding:14px;border-radius:10px;
background:linear-gradient(135deg,rgba(0,212,255,.08),rgba(168,85,247,.08));
border:1px dashed rgba(0,212,255,.2);
text-align:center;
}
.arch-box .engine-core span{
font-family:'Orbitron',monospace;font-size:20px;font-weight:700;
color:var(--cyan);
}
.arch-box .engine-core small{display:block;font-size:11px;color:var(--text3);margin-top:4px}
/* arrows */
.arch-arrow{
display:flex;align-items:center;justify-content:center;
padding:0 8px;min-width:60px;
}
.arch-arrow svg{width:48px;height:48px;filter:drop-shadow(0 0 6px rgba(0,212,255,.4))}
/* ═══ 区块2: 模块矩阵 ═══ */
.module-grid{
display:grid;
grid-template-columns:repeat(auto-fill,minmax(180px,1fr));
gap:16px;
}
.mod-card{
background:var(--card);
border:1px solid var(--border);
border-radius:14px;padding:20px 16px;
text-align:center;cursor:default;
transition:all .3s ease;
position:relative;overflow:hidden;
}
.mod-card::after{
content:'';position:absolute;inset:0;border-radius:14px;
opacity:0;transition:opacity .3s;
pointer-events:none;
}
.mod-card:hover{transform:translateY(-4px);border-color:rgba(0,212,255,.3)}
.mod-card:hover::after{opacity:1}
.mod-card .mod-icon{font-size:28px;margin-bottom:8px;display:block}
.mod-card .mod-name{font-size:14px;font-weight:500;color:var(--text);margin-bottom:6px}
.mod-card .mod-count{
font-family:'Orbitron',monospace;font-size:22px;font-weight:700;
display:block;margin-bottom:2px;
}
.mod-card .mod-label{font-size:11px;color:var(--text3)}
/* glow colors */
.mod-card[data-cat="msg"]{--glow:var(--blue)}.mod-card[data-cat="msg"] .mod-count{color:var(--blue)}
.mod-card[data-cat="msg"]::after{box-shadow:inset 0 0 30px rgba(59,130,246,.1)}
.mod-card[data-cat="friend"]{--glow:var(--green)}.mod-card[data-cat="friend"] .mod-count{color:var(--green)}
.mod-card[data-cat="friend"]::after{box-shadow:inset 0 0 30px rgba(0,255,136,.1)}
.mod-card[data-cat="group"]{--glow:var(--purple)}.mod-card[data-cat="group"] .mod-count{color:var(--purple)}
.mod-card[data-cat="group"]::after{box-shadow:inset 0 0 30px rgba(168,85,247,.1)}
.mod-card[data-cat="pay"]{--glow:var(--gold)}.mod-card[data-cat="pay"] .mod-count{color:var(--gold)}
.mod-card[data-cat="pay"]::after{box-shadow:inset 0 0 30px rgba(255,215,0,.1)}
.mod-card[data-cat="secure"]{--glow:var(--red)}.mod-card[data-cat="secure"] .mod-count{color:var(--red)}
.mod-card[data-cat="secure"]::after{box-shadow:inset 0 0 30px rgba(255,71,87,.1)}
.mod-card[data-cat="social"]{--glow:var(--pink)}.mod-card[data-cat="social"] .mod-count{color:var(--pink)}
.mod-card[data-cat="social"]::after{box-shadow:inset 0 0 30px rgba(236,72,153,.1)}
.mod-card[data-cat="media"]{--glow:var(--orange)}.mod-card[data-cat="media"] .mod-count{color:var(--orange)}
.mod-card[data-cat="media"]::after{box-shadow:inset 0 0 30px rgba(255,107,53,.1)}
.mod-card[data-cat="tool"]{--glow:var(--cyan)}.mod-card[data-cat="tool"] .mod-count{color:var(--cyan)}
.mod-card[data-cat="tool"]::after{box-shadow:inset 0 0 30px rgba(0,212,255,.1)}
.mod-card[data-cat="setting"]{--glow:#8b5cf6}.mod-card[data-cat="setting"] .mod-count{color:#8b5cf6}
.mod-card[data-cat="setting"]::after{box-shadow:inset 0 0 30px rgba(139,92,246,.1)}
/* ═══ 区块3: 调用流程 ═══ */
.flow-track{
display:flex;align-items:stretch;gap:0;
overflow-x:auto;padding:20px 0;
}
.flow-step{
flex:1;min-width:120px;
display:flex;flex-direction:column;align-items:center;
position:relative;
}
.flow-step .step-node{
width:100%;padding:16px 10px;
background:var(--card);border:1px solid var(--border);
border-radius:12px;text-align:center;
position:relative;z-index:1;
transition:all .3s;
}
.flow-step .step-node:hover{
border-color:var(--cyan);
box-shadow:0 0 20px rgba(0,212,255,.15);
}
.flow-step .step-num{
font-family:'Orbitron',monospace;font-size:11px;
color:var(--cyan);opacity:.6;display:block;margin-bottom:4px;
}
.flow-step .step-icon{font-size:24px;display:block;margin-bottom:6px}
.flow-step .step-text{font-size:12px;color:var(--text);font-weight:500}
.flow-step .step-desc{font-size:10px;color:var(--text3);margin-top:4px}
.flow-arrow{
display:flex;align-items:center;flex-shrink:0;
padding:0 2px;color:var(--cyan);opacity:.5;
font-size:18px;margin-top:20px;
}
/* ═══ 区块4: 统计看板 ═══ */
.stats-grid{
display:grid;
grid-template-columns:repeat(auto-fit,minmax(200px,1fr));
gap:20px;
}
.stat-card{
background:var(--card);border:1px solid var(--border);
border-radius:16px;padding:28px 24px;
text-align:center;position:relative;overflow:hidden;
}
.stat-card::before{
content:'';position:absolute;top:-40px;right:-40px;
width:100px;height:100px;border-radius:50%;
opacity:.06;
}
.stat-card:nth-child(1)::before{background:var(--cyan)}
.stat-card:nth-child(2)::before{background:var(--green)}
.stat-card:nth-child(3)::before{background:var(--purple)}
.stat-card:nth-child(4)::before{background:var(--gold)}
.stat-card:nth-child(5)::before{background:var(--orange)}
.stat-card .stat-icon{font-size:32px;margin-bottom:10px;display:block}
.stat-card .stat-value{
font-family:'Orbitron',monospace;font-weight:900;
font-size:clamp(32px,4vw,44px);display:block;
margin-bottom:4px;
}
.stat-card:nth-child(1) .stat-value{color:var(--cyan)}
.stat-card:nth-child(2) .stat-value{color:var(--green)}
.stat-card:nth-child(3) .stat-value{color:var(--purple)}
.stat-card:nth-child(4) .stat-value{color:var(--gold)}
.stat-card:nth-child(5) .stat-value{color:var(--orange)}
.stat-card .stat-label{font-size:13px;color:var(--text2);letter-spacing:1px}
.progress-wrap{
margin-top:32px;background:var(--card);
border:1px solid var(--border);border-radius:16px;
padding:28px 32px;
}
.progress-header{
display:flex;justify-content:space-between;align-items:center;
margin-bottom:16px;
}
.progress-header span{font-size:14px;color:var(--text2)}
.progress-header strong{
font-family:'Orbitron',monospace;font-size:24px;color:var(--green);
}
.progress-bar{
height:12px;border-radius:6px;
background:rgba(255,255,255,.06);overflow:hidden;
}
.progress-fill{
height:100%;border-radius:6px;
background:linear-gradient(90deg,var(--cyan),var(--green));
width:0;transition:width 1.8s cubic-bezier(.25,.46,.45,.94);
box-shadow:0 0 16px rgba(0,255,136,.3);
position:relative;
}
.progress-fill::after{
content:'';position:absolute;inset:0;
background:linear-gradient(90deg,transparent 0%,rgba(255,255,255,.2) 50%,transparent 100%);
animation:shimmer 2s infinite;
}
@keyframes shimmer{
0%{transform:translateX(-100%)}
100%{transform:translateX(100%)}
}
/* ═══ footer ═══ */
.footer{
text-align:center;padding:48px 0 24px;
font-size:12px;color:var(--text3);
font-family:'Orbitron',monospace;
}
/* ═══ animations ═══ */
@keyframes fadeUp{
from{opacity:0;transform:translateY(30px)}
to{opacity:1;transform:translateY(0)}
}
.anim{opacity:0;transform:translateY(30px)}
.anim.visible{animation:fadeUp .6s ease forwards}
/* ═══ responsive ═══ */
@media(max-width:1024px){
.arch-grid{
grid-template-columns:1fr;gap:16px;
}
.arch-arrow{
transform:rotate(90deg);padding:8px 0;min-width:unset;
}
.arch-arrow svg{width:36px;height:36px}
.flow-track{flex-wrap:wrap;gap:8px}
.flow-arrow{display:none}
}
@media(max-width:640px){
.wrapper{padding:0 16px 60px}
.module-grid{grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:10px}
.stats-grid{grid-template-columns:repeat(2,1fr);gap:12px}
}
/* ═══ legend ═══ */
.legend{
display:flex;flex-wrap:wrap;gap:12px 24px;
margin-bottom:24px;
}
.legend-item{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text2)}
.legend-dot{width:10px;height:10px;border-radius:3px}
/* ═══ tooltip for category filter ═══ */
.cat-filters{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:24px}
.cat-btn{
padding:6px 14px;border-radius:20px;font-size:12px;
border:1px solid rgba(255,255,255,.1);background:transparent;
color:var(--text2);cursor:pointer;transition:all .25s;
font-family:'Noto Sans SC',sans-serif;
}
.cat-btn:hover,.cat-btn.active{
border-color:var(--cyan);color:var(--cyan);
background:rgba(0,212,255,.08);
}
</style>
</head>
<body>
<canvas id="bg-canvas"></canvas>
<div class="wrapper">
<!-- ═══════ HERO ═══════ -->
<header class="hero anim">
<h1>JIQI SDK · API ATLAS</h1>
<p class="sub">WorkPhone SDK v3.0 Architecture</p>
<span class="version">FastAPI :8899 · WeChat 8.0.56 · 98 Endpoints</span>
</header>
<!-- ═══════ 区块1: 系统架构总览 ═══════ -->
<section class="section anim">
<div class="section-title">01 · SYSTEM ARCHITECTURE</div>
<div class="arch-grid">
<!-- 调用方 -->
<div class="arch-box caller">
<div class="box-icon">📡</div>
<h3>CALLERS</h3>
<ul>
<li><span class="dot"></span>存客宝服务器ThinkPHP</li>
<li><span class="dot"></span>触客宝 H5 / 任意应用</li>
<li><span class="dot"></span>curl / Postman 直调</li>
<li><span class="dot"></span>WebSocket 推送订阅</li>
</ul>
</div>
<!-- 箭头 → -->
<div class="arch-arrow">
<svg viewBox="0 0 48 48" fill="none"><path d="M8 24h28M30 18l8 6-8 6" stroke="var(--cyan)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><animate attributeName="opacity" values="1;.4;1" dur="2s" repeatCount="indefinite"/></path></svg>
</div>
<!-- 机擎SDK -->
<div class="arch-box engine">
<div class="box-icon"></div>
<h3>JIQI SDK ENGINE</h3>
<ul>
<li><span class="dot"></span>FastAPI 统一路由层 (:8899)</li>
<li><span class="dot"></span>参数校验 · Pydantic Models</li>
<li><span class="dot"></span>导航缓存 · NavigationCache</li>
<li><span class="dot"></span>操作日志 · OperationLogger</li>
<li><span class="dot"></span>防封策略 · AntiDetection</li>
</ul>
<div class="engine-core">
<span>96</span>
<small>WeChatADBEngine Actions</small>
</div>
</div>
<!-- 箭头 → -->
<div class="arch-arrow">
<svg viewBox="0 0 48 48" fill="none"><path d="M8 24h28M30 18l8 6-8 6" stroke="var(--orange)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><animate attributeName="opacity" values="1;.4;1" dur="2s" begin=".5s" repeatCount="indefinite"/></path></svg>
</div>
<!-- Android 设备 -->
<div class="arch-box device">
<div class="box-icon">📱</div>
<h3>ANDROID DEVICE</h3>
<ul>
<li><span class="dot"></span>ADB / USB 连接层</li>
<li><span class="dot"></span>微信 8.0.56 (目标应用)</li>
<li><span class="dot"></span>uiautomator2 · UI自动化</li>
<li><span class="dot"></span>XPath / AccessibilityNode</li>
</ul>
</div>
</div>
</section>
<!-- ═══════ 区块2: API 模块矩阵 ═══════ -->
<section class="section anim">
<div class="section-title">02 · API MODULE MATRIX</div>
<div class="legend">
<div class="legend-item"><span class="legend-dot" style="background:var(--blue)"></span>消息</div>
<div class="legend-item"><span class="legend-dot" style="background:var(--green)"></span>好友</div>
<div class="legend-item"><span class="legend-dot" style="background:var(--purple)"></span>群聊</div>
<div class="legend-item"><span class="legend-dot" style="background:var(--gold)"></span>支付</div>
<div class="legend-item"><span class="legend-dot" style="background:var(--red)"></span>安全</div>
<div class="legend-item"><span class="legend-dot" style="background:var(--pink)"></span>社交</div>
<div class="legend-item"><span class="legend-dot" style="background:var(--orange)"></span>媒体</div>
<div class="legend-item"><span class="legend-dot" style="background:var(--cyan)"></span>工具</div>
<div class="legend-item"><span class="legend-dot" style="background:#8b5cf6"></span>设置</div>
</div>
<div class="cat-filters">
<button class="cat-btn active" data-filter="all">全部 (23)</button>
<button class="cat-btn" data-filter="msg">消息</button>
<button class="cat-btn" data-filter="friend">好友</button>
<button class="cat-btn" data-filter="group">群聊</button>
<button class="cat-btn" data-filter="pay">支付</button>
<button class="cat-btn" data-filter="secure">安全</button>
<button class="cat-btn" data-filter="social">社交</button>
<button class="cat-btn" data-filter="media">媒体</button>
<button class="cat-btn" data-filter="tool">工具</button>
<button class="cat-btn" data-filter="setting">设置</button>
</div>
<div class="module-grid" id="moduleGrid"></div>
</section>
<!-- ═══════ 区块3: 调用流程 ═══════ -->
<section class="section anim">
<div class="section-title">03 · REQUEST LIFECYCLE</div>
<div class="flow-track" id="flowTrack"></div>
</section>
<!-- ═══════ 区块4: 统计看板 ═══════ -->
<section class="section anim">
<div class="section-title">04 · DASHBOARD</div>
<div class="stats-grid">
<div class="stat-card">
<span class="stat-icon">🔗</span>
<span class="stat-value" data-target="98">0</span>
<span class="stat-label">总端点数</span>
</div>
<div class="stat-card">
<span class="stat-icon">⚙️</span>
<span class="stat-value" data-target="96">0</span>
<span class="stat-label">引擎方法</span>
</div>
<div class="stat-card">
<span class="stat-icon">📦</span>
<span class="stat-value" data-target="23">0</span>
<span class="stat-label">功能模块</span>
</div>
<div class="stat-card">
<span class="stat-icon"></span>
<span class="stat-value" data-target="100" data-suffix="%">0</span>
<span class="stat-label">端点覆盖率</span>
</div>
<div class="stat-card">
<span class="stat-icon">🚀</span>
<span class="stat-value" data-target="97" data-suffix="%">0</span>
<span class="stat-label">开发进度</span>
</div>
</div>
<div class="progress-wrap anim">
<div class="progress-header">
<span>SDK 整体开发进度</span>
<strong>97%</strong>
</div>
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
</div>
</section>
<footer class="footer">
JIQI SDK v3.0 · WorkPhone Engine · Built for CunKeBao<br>
Generated 2026-03-14
</footer>
</div>
<script>
/* ═══════════════════════════════════════
Background particle canvas
═══════════════════════════════════════ */
(function(){
const c=document.getElementById('bg-canvas'),ctx=c.getContext('2d');
let w,h,pts=[];
function resize(){w=c.width=innerWidth;h=c.height=innerHeight;pts=[];
for(let i=0;i<60;i++) pts.push({x:Math.random()*w,y:Math.random()*h,vx:(Math.random()-.5)*.3,vy:(Math.random()-.5)*.3,r:Math.random()*1.5+.5});
}
function draw(){
ctx.clearRect(0,0,w,h);
for(const p of pts){
p.x+=p.vx;p.y+=p.vy;
if(p.x<0)p.x=w;if(p.x>w)p.x=0;if(p.y<0)p.y=h;if(p.y>h)p.y=0;
ctx.beginPath();ctx.arc(p.x,p.y,p.r,0,Math.PI*2);
ctx.fillStyle='rgba(0,212,255,.5)';ctx.fill();
}
for(let i=0;i<pts.length;i++) for(let j=i+1;j<pts.length;j++){
const dx=pts[i].x-pts[j].x,dy=pts[i].y-pts[j].y,d=Math.sqrt(dx*dx+dy*dy);
if(d<150){ctx.beginPath();ctx.moveTo(pts[i].x,pts[i].y);ctx.lineTo(pts[j].x,pts[j].y);
ctx.strokeStyle=`rgba(0,212,255,${(.15*(1-d/150)).toFixed(3)})`;ctx.stroke();}
}
requestAnimationFrame(draw);
}
resize();draw();
addEventListener('resize',resize);
})();
/* ═══════════════════════════════════════
Module data & render
═══════════════════════════════════════ */
const MODULES=[
{name:'消息管理',count:8,icon:'💬',cat:'msg'},
{name:'好友管理',count:8,icon:'👤',cat:'friend'},
{name:'群聊管理',count:10,icon:'👥',cat:'group'},
{name:'标签管理',count:6,icon:'🏷️',cat:'tool'},
{name:'朋友圈管理',count:8,icon:'🌐',cat:'social'},
{name:'个人设置',count:6,icon:'⚙️',cat:'setting'},
{name:'账号安全',count:9,icon:'🛡️',cat:'secure'},
{name:'收藏管理',count:2,icon:'⭐',cat:'tool'},
{name:'支付',count:7,icon:'💰',cat:'pay'},
{name:'聊天设置',count:3,icon:'🔧',cat:'setting'},
{name:'小程序',count:1,icon:'📱',cat:'tool'},
{name:'公众号',count:1,icon:'📰',cat:'media'},
{name:'视频号',count:5,icon:'🎬',cat:'media'},
{name:'扫一扫',count:4,icon:'📷',cat:'tool'},
{name:'通话',count:2,icon:'📞',cat:'msg'},
{name:'群发助手',count:1,icon:'📢',cat:'msg'},
{name:'搜一搜',count:1,icon:'🔍',cat:'tool'},
{name:'看一看',count:1,icon:'👀',cat:'social'},
{name:'微信运动',count:2,icon:'🏃',cat:'social'},
{name:'位置分享',count:2,icon:'📍',cat:'tool'},
{name:'表情管理',count:2,icon:'😀',cat:'social'},
{name:'文件管理',count:2,icon:'📁',cat:'tool'},
{name:'设置管理',count:5,icon:'🎛️',cat:'setting'},
];
const grid=document.getElementById('moduleGrid');
MODULES.forEach((m,i)=>{
const card=document.createElement('div');
card.className='mod-card anim';
card.dataset.cat=m.cat;
card.style.animationDelay=`${i*40}ms`;
card.innerHTML=`
<span class="mod-icon">${m.icon}</span>
<div class="mod-name">${m.name}</div>
<span class="mod-count">${m.count}</span>
<span class="mod-label">endpoints</span>`;
grid.appendChild(card);
});
/* category filter */
document.querySelectorAll('.cat-btn').forEach(btn=>{
btn.addEventListener('click',()=>{
document.querySelectorAll('.cat-btn').forEach(b=>b.classList.remove('active'));
btn.classList.add('active');
const f=btn.dataset.filter;
document.querySelectorAll('.mod-card').forEach(c=>{
c.style.display=(f==='all'||c.dataset.cat===f)?'':'none';
});
});
});
/* ═══════════════════════════════════════
Flow steps render
═══════════════════════════════════════ */
const STEPS=[
{icon:'🌐',text:'HTTP 请求',desc:'POST /api/v1/*'},
{icon:'🚦',text:'FastAPI 路由',desc:'统一路由分发'},
{icon:'🔍',text:'参数校验',desc:'Pydantic 验证'},
{icon:'⚡',text:'Engine.execute()',desc:'WeChatADBEngine'},
{icon:'💾',text:'导航缓存',desc:'NavigationCache'},
{icon:'🔌',text:'ADB 命令',desc:'shell / input'},
{icon:'🤖',text:'UI 自动化',desc:'uiautomator2'},
{icon:'📤',text:'结果返回',desc:'JSON Response'},
{icon:'📝',text:'日志记录',desc:'OperationLogger'},
];
const track=document.getElementById('flowTrack');
STEPS.forEach((s,i)=>{
if(i>0){
const arrow=document.createElement('div');
arrow.className='flow-arrow';arrow.textContent='→';
track.appendChild(arrow);
}
const step=document.createElement('div');
step.className='flow-step anim';
step.style.animationDelay=`${i*80}ms`;
step.innerHTML=`
<div class="step-node">
<span class="step-num">${String(i+1).padStart(2,'0')}</span>
<span class="step-icon">${s.icon}</span>
<div class="step-text">${s.text}</div>
<div class="step-desc">${s.desc}</div>
</div>`;
track.appendChild(step);
});
/* ═══════════════════════════════════════
Intersection Observer for animations
═══════════════════════════════════════ */
const obs=new IntersectionObserver((entries)=>{
entries.forEach(e=>{
if(e.isIntersecting){e.target.classList.add('visible');obs.unobserve(e.target);}
});
},{threshold:.15});
document.querySelectorAll('.anim').forEach(el=>obs.observe(el));
/* ═══════════════════════════════════════
Number counter animation
═══════════════════════════════════════ */
const numObs=new IntersectionObserver((entries)=>{
entries.forEach(e=>{
if(!e.isIntersecting) return;
const el=e.target, target=+el.dataset.target, suffix=el.dataset.suffix||'';
let start=0;const dur=1200,t0=performance.now();
(function tick(now){
const p=Math.min((now-t0)/dur,1);
const ease=1-Math.pow(1-p,3);
el.textContent=Math.round(ease*target)+suffix;
if(p<1) requestAnimationFrame(tick);
})(t0);
numObs.unobserve(el);
});
},{threshold:.5});
document.querySelectorAll('.stat-value[data-target]').forEach(el=>numObs.observe(el));
/* progress bar */
const pObs=new IntersectionObserver((entries)=>{
entries.forEach(e=>{
if(e.isIntersecting){
document.getElementById('progressFill').style.width='97%';
pObs.unobserve(e.target);
}
});
},{threshold:.3});
pObs.observe(document.querySelector('.progress-wrap'));
</script>
</body>
</html>

View File

@@ -0,0 +1,557 @@
# 防封策略 · 全平台全层级(必读)
> **版本**: 1.0.0 | **更新**: 2026-03-14
> **定位**: 工作手机SDK开发**强制前置读取文档**——每次开发涉及设备控制、消息发送、自动化操作的功能前,必须先读本文档
> **维护**: 阿机(设备端)+ 阿桥(服务端/中间层)+ 阿服(部署环境)
> **来源**: 全网调研 + GitHub 开源项目 + 行业实战经验
---
## 〇、核心原则
```
防封 = 设备隔离 × 环境伪装 × 行为拟人 × 频率控制 × 内容差异 × 监控应急
```
任何一个维度缺失都可能导致封号。**技术防封是基础,行为可信是内核**。
**风控三维检测模型**(所有平台通用):
| 维度 | 检测内容 | 封号权重 |
|------|----------|:--------:|
| **设备指纹** | IMEI/MAC/Android ID/蓝牙/传感器/屏幕/CPU | 40% |
| **行为特征** | 操作频率/间隔规律/触摸轨迹/浏览深度/操作链 | 35% |
| **网络环境** | IP归属/IP关联/代理特征/DNS/时区一致性 | 25% |
---
## 一、设备端防封Agent层 — 阿机负责)
### 1.1 设备指纹隔离(最重要)
**铁律一机一号一卡一IP**
平台通过 20+ 维度特征组合生成设备指纹,任何两台设备指纹相似度过高即判定关联:
| 指纹维度 | 具体项 | 伪装方式 |
|----------|--------|----------|
| 硬件标识 | IMEI、MEID、序列号 | Magisk模块修改/Xposed Hook |
| 网络标识 | MAC地址、蓝牙地址 | 系统级随机化 |
| 软件标识 | Android ID、GSF ID、GAID | 每次刷机重新生成 |
| 系统属性 | Build.FINGERPRINT/MODEL/BRAND等 | Build-var-Spoof (LSPosed模块) |
| 传感器 | 加速度计/陀螺仪基线噪声 | 添加微小随机偏移 |
| 屏幕 | 分辨率、像素密度、刷新率 | 与真机型号一致 |
**推荐工具**
- **DeviceSpoofLab-Hooks** (github.com/yubunus/DeviceSpoofLab-Hooks) — 126+ 系统属性 Hook需 LSPosed
- **Build-var-Spoof** (github.com/LSPosed/Build-var-Spoof) — C++ 级 Build 属性伪装
- **AndroidFaker** — 轻量级设备 ID 伪装 Xposed 模块
**开发注意**
- Agent 启动时采集并上报设备指纹摘要MD5服务端检测同批设备是否存在指纹碰撞
- 每台设备在初始化时必须确保指纹唯一性,碰撞率要求 < 0.01%
- 设备管理服务DeviceSvc增加 `fingerprint_hash` 字段用于关联检测
### 1.2 Root 隐藏Frida/Hook 前置)
微信等应用会主动检测 Root 状态检测到即降低账号权重或直接限制功能
| 方案 | 工具 | 说明 |
|------|------|------|
| **Root 框架** | Magisk (Zygisk) KernelSU/APatch | KernelSU 隐藏性更强 |
| **Root 隐藏** | Shamiko v4.14+ | 替代已废弃的 MagiskHide配合 Zygisk |
| **应用列表隐藏** | HMA-OSS (Hide My Applist) | 阻止微信扫描已安装的 Root 相关 APP |
| **完整性修复** | TrickStore + Play Integrity Fork | 通过 Google 安全检查 |
| **Bootloader 伪装** | 各方案内置 | Bootloader 状态报告为已锁定 |
**配置步骤**每台设备部署时必须执行
1. 刷入 Magisk/KernelSU 启用 Zygisk
2. 安装 Shamiko 配置 DenyList将微信抖音等加入)→ **关闭** DenyList 强制模式
3. 安装 HMA-OSS 隐藏 Magisk ManagerFrida ServerTerminal
4. 安装 TrickStore + Play Integrity Fork
5. Hunter/TB Checker 验证隐藏效果
**开发注意**
- `sdk/agent/install.sh` 必须包含上述模块的自动安装流程
- Agent 启动自检增加 Root 隐藏状态验证
- 设备能力上报增加 `root_hidden: bool` 字段
### 1.3 Frida 反检测Hook通道关键
微信等应用有专门的 Frida 检测逻辑检测到即触发风控Frida 检测向量共 16
| # | 检测向量 | 说明 | 绕过方式 |
|---|----------|------|----------|
| 1 | 进程名 frida-server | 进程列表扫描 | 重命名为随机字符串 |
| 2 | /proc/maps libfrida-agent.so | 内存映射检测 | 随机化库名 |
| 3 | 线程名 gum-js-loop/gmain/gdbus | 线程枚举 | 替换为系统常见线程名 |
| 4 | memfd 名称 | 内存文件描述符 | 随机化 |
| 5 | 符号名 frida_agent_main | 符号表扫描 | 混淆所有导出符号 |
| 6 | SELinux 标签 | 安全上下文检测 | 修改标签 |
| 7 | libc hooks 残留 | 函数 Hook 痕迹 | 深度清理 |
| 8 | D-Bus 服务名 | IPC 通信特征 | 替换服务名 |
| 9 | 默认端口 27042 | 端口扫描 | 使用随机端口 |
| 10 | 二进制字符串 "frida" | strings 扫描 | 全量替换 |
| 11-16 | 内部C符号/GType/临时路径/构建标记/资产目录 | 深度扫描 | 全量 patch |
**推荐方案(按优先级)**
| 方案 | GitHub 地址 | Stars | 特点 |
|------|------------|:-----:|------|
| **Phantom-Frida** | TheQmaks/phantom-frida | ★★★ | 90+ patches16 向量全覆盖每周自动构建 |
| **StrongR-Frida** | hzzheyang/strongR-frida-android | 1k+ | 跟随官方版本更新社区活跃 |
| **Florida** | Ylarod/Florida | ★★ | 基础反检测自动跟随 Frida 发布 |
| **Undetected-Frida** | ultrafunkamsterdam/undetected-frida | ★★ | 8 patch 文件较轻量 |
**开发注意**
- `sdk/agent/hook/frida_manager.py` Frida Server 启动必须使用反检测版本
- 配置文件 `config.json` 增加 `frida_server_path` 指向反检测版二进制
- 端口配置为每台设备随机生成范围 10000-60000写入设备配置持久化
- **严禁**使用默认端口 27042 和默认进程名
### 1.4 UIAutomator2 反检测u2通道
应用可通过以下方式检测 u2 自动化
| 检测方式 | 说明 | 绕过策略 |
|----------|------|----------|
| AccessibilityService 检测 | 检查是否有活跃的无障碍服务 | 仅在操作时启用操作完立即关闭 |
| 触摸事件来源检测 | 程序注入的触摸事件有标记位 | 使用 `input tap` 替代 API 注入 |
| 操作间隔一致性 | 每次间隔完全相同 | 随机延迟 + 高斯分布 |
| 触摸轨迹检测 | 直线点击无移动轨迹 | 模拟贝塞尔曲线滑动轨迹 |
| 输入速度检测 | 瞬间输入完整文本 | 逐字输入 + 随机间隔(50-200ms) |
**开发注意(所有 Skill 必须遵守)**
```python
# sdk/agent/skills/base.py 中必须实现的防检测基础方法
import random
import time
import math
def human_delay(min_sec=0.5, max_sec=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(device, text, field_selector):
"""拟人输入:逐字输入 + 随机间隔 + 偶尔打错重输"""
field_selector.click()
human_delay(0.3, 0.8)
for i, char in enumerate(text):
device.send_keys(char)
time.sleep(random.uniform(0.05, 0.2))
# 5% 概率模拟打错并退格
if random.random() < 0.05 and i < len(text) - 1:
device.send_keys(random.choice('abcde'))
time.sleep(random.uniform(0.1, 0.3))
device.press('del')
time.sleep(random.uniform(0.1, 0.2))
def human_swipe(device, start, end, duration=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)
device.swipe_ext("bezier", points=[start, (ctrl_x, ctrl_y), end], duration=duration)
def random_browse(device, duration_sec=30):
"""随机浏览:模拟真人无目的浏览行为"""
end_time = time.time() + duration_sec
while time.time() < end_time:
action = random.choice(['scroll', 'pause', 'tap_random'])
if action == 'scroll':
human_swipe(device, (540, 1500), (540, 500))
human_delay(2.0, 5.0)
elif action == 'pause':
human_delay(3.0, 8.0)
elif action == 'tap_random':
# 在安全区域随机点击
device.click(random.randint(100, 980), random.randint(300, 1800))
human_delay(1.0, 3.0)
device.press('back')
human_delay(1.0, 2.0)
```
### 1.5 模拟器检测绕过
**强烈建议使用真机**若必须用模拟器
| 检测点 | 绕过方式 |
|--------|----------|
| Build.HARDWARE/PRODUCT "goldfish"/"sdk" | 修改 Build.prop |
| 传感器缺失加速度计/陀螺仪 | 模拟传感器数据 |
| 电池状态永远充电中 | Hook 电池 API |
| IMEI 全零 | 使用 AndroidFaker 设置真实 IMEI |
| /dev/qemu_pipe 等特征文件 | 隐藏相关文件路径 |
**参考项目**
- **Anti-EmuDetector** (github.com/u0pattern/Anti-EmuDetector) 绕过模拟器检测支持 Flutter
- **fake-android** (github.com/kkoooqq/fake-android) 多设备群控防检测支持 RPC/NAT
---
## 二、服务端防封SDK Server — 阿机+阿桥负责)
### 2.1 请求频率控制(核心防封机制)
**分三级限流**
| 级别 | 对象 | 策略 | 实现 |
|------|------|------|------|
| **L1 全局** | 整个 SDK | 令牌桶峰值 100 req/min | Redis + 滑动窗口 |
| **L2 设备** | 单台设备 | 每分钟 10 次操作 | 设备维度 Redis key |
| **L3 动作** | 单一操作类型 | 见下表平台限制 | 动作维度独立计数器 |
**各平台操作频率上限**写入 Redis 配置可动态调整
| 平台 | 操作 | 频率上限 | 冷却时间 | 日上限 |
|------|------|:--------:|:--------:|:------:|
| 微信 | 主动加好友 | 1次/3-5min | 随机 180-300s | 新号10/老号50 |
| 微信 | 发消息 | 1次/30-60s | 随机 30-60s | 200条 |
| 微信 | 发朋友圈 | 1次/2h | 随机 7200-10800s | 5条 |
| 微信 | 群发 | 1次/5min | 随机 300-600s | 50次 |
| 抖音 | 私信 | 1次/60-120s | 随机 60-120s | 100条 |
| 抖音 | 关注 | 1次/30-60s | 随机 30-60s | 200次 |
| 小红书 | 私信 | 1次/60-180s | 随机 60-180s | 50条 |
| 小红书 | 点赞 | 1次/15-30s | 随机 15-30s | 300次 |
| 闲鱼 | 私信 | 1次/60-120s | 随机 60-120s | 80条 |
**代码实现要点**`sdk/app/services/` 增加 `rate_limiter.py`
```python
# 限流器核心逻辑
class AntiDetectRateLimiter:
"""三级防封限流器"""
PLATFORM_LIMITS = {
"wechat": {
"add_friend": {"interval": (180, 300), "daily_max": 50},
"send_message": {"interval": (30, 60), "daily_max": 200},
"post_moment": {"interval": (7200, 10800), "daily_max": 5},
},
"douyin": {
"send_message": {"interval": (60, 120), "daily_max": 100},
"follow": {"interval": (30, 60), "daily_max": 200},
},
# ... 其他平台
}
async def check_and_wait(self, device_id, platform, action):
"""检查频率限制,必要时等待"""
limit = self.PLATFORM_LIMITS[platform][action]
# 1. 检查日上限
daily_count = await self.get_daily_count(device_id, platform, action)
if daily_count >= limit["daily_max"]:
raise DailyLimitExceeded(f"{platform}.{action} 已达日上限 {limit['daily_max']}")
# 2. 计算随机等待时间
wait = random.uniform(*limit["interval"])
last_time = await self.get_last_action_time(device_id, platform, action)
elapsed = time.time() - last_time
if elapsed < wait:
await asyncio.sleep(wait - elapsed)
# 3. 记录本次操作
await self.record_action(device_id, platform, action)
```
### 2.2 任务调度防封
| 策略 | 说明 |
|------|------|
| **时段控制** | 操作集中在 8:00-22:00夜间仅心跳 |
| **渐进式启动** | 新号前7天操作量每天递增 20% |
| **随机空闲** | 每30-60分钟插入5-15分钟空闲期 |
| **穿插真实操作** | 操作间穿插浏览朋友圈/刷短视频等"养号"行为 |
| **设备轮休** | 每台设备每天最多工作 14 小时 |
| **批量错开** | 多设备同类操作错开执行避免同一秒并发 |
### 2.3 IP 策略
| 策略 | 说明 | 实现 |
|------|------|------|
| **设备绑定 IP** | 每台设备长期使用固定 IP | 配置表绑定 |
| **IP-设备一致性** | IP 归属地与设备 SIM 卡归属一致 | 自动校验 |
| **代理池轮换** | 高频操作时使用住宅 IP 代理池 | Redis 管理 |
| **异常切换** | 收到 403/429 时立即切换 IP | 熔断回调 |
| **DNS 一致性** | 使用与 IP 归属地一致的 DNS | 设备端配置 |
### 2.4 unified.py 防封集成点
`sdk/app/routers/unified.py` 的请求处理流程中增加防封中间件
```
请求进入 → 鉴权 → 【防封检查】→ 通道路由 → 执行 → 结果回传
├── 1. 频率限制检查RateLimiter
├── 2. 日上限检查
├── 3. 随机延迟注入
├── 4. 养号策略检查(新号限制)
└── 5. 敏感词过滤
```
---
## 三、应用层防封(各平台 Skill — 阿机+阿桥负责)
### 3.1 微信防封策略(最关键)
#### 3.1.1 账号生命周期管理
| 阶段 | 天数 | 操作限制 | 养号任务 |
|------|:----:|----------|----------|
| **新号期** | 0-7天 | 每日加人5发消息20 | 完善资料实名认证绑银行卡发3条朋友圈 |
| **成长期** | 8-30天 | 每日加人15发消息80 | 日常聊天支付阅读文章玩小程序 |
| **成熟期** | 30天+ | 每日加人50发消息200 | 保持活跃度定期发朋友圈正常社交 |
#### 3.1.2 微信高危操作红线
| 操作 | 红线 | 后果 |
|------|------|------|
| 同设备登3+账号 | **严禁** | 所有账号关联封号 |
| 频繁切换账号 | 间隔 < 2h **严禁** | 降权/限制登录 |
| 使用"附近的人" | **新号严禁** | 直接封号 |
| 群发完全相同内容 | **严禁** | 内容违规 + 封号 |
| 被动加人 > 60/min | **严禁** | 立即限制 |
| 主动加人 > 40/hour | **严禁** | 功能限制 |
| 使用非官方客户端 | **严禁** | 永久封号 |
#### 3.1.3 微信操作编码规范
所有微信 Skill 方法必须遵守:
```python
# WeChatSkill 中每个 action 必须包含的防封逻辑
async def send_message(self, to_id, content, **kwargs):
# 1. 频率检查
await self.rate_limiter.check_and_wait(self.device_id, "wechat", "send_message")
# 2. 内容差异化处理
content = self.diversify_content(content)
# 3. 操作前:模拟自然行为链
await self.simulate_natural_flow([
("open_chat", to_id), # 打开对话
("scroll_history", 2), # 浏览2-3条历史消息
("pause", (1.0, 3.0)), # 停顿"阅读"
("type_message", content), # 逐字输入
("pause", (0.5, 1.5)), # 输入后停顿
("send", None), # 发送
])
# 4. 操作后:随机后续行为
if random.random() < 0.3:
await self.browse_moments(duration=random.randint(10, 30))
def diversify_content(self, content):
"""内容差异化:同义词替换 + 变量注入"""
# 替换变量 {时间} {称呼} 等
content = content.replace("{时间}", self._get_greeting_by_time())
content = content.replace("{称呼}", random.choice(["亲", "您好", "hi"]))
# 随机添加不可见字符(零宽空格)增加唯一性
if random.random() < 0.5:
pos = random.randint(0, len(content))
content = content[:pos] + '\u200b' + content[pos:]
return content
```
### 3.2 抖音防封策略
| 检测维度 | 策略 |
|----------|------|
| 行为链分析 | 先浏览首页 30s → 看2-3个视频 → 再执行目标操作 |
| 传感器数据 | 定期模拟陀螺仪/加速度变化(模拟手持抖动) |
| 操作节奏 | 每小时强制休息 10 分钟 |
| 互动比率 | 点赞率 ≤ 20%,评论率 5-10%,避免全量操作 |
| 视频观看 | 每个视频停留 > 15s不可秒滑 |
### 3.3 小红书防封策略
**小红书设备指纹检测最为严格**
| 维度 | 要求 |
|------|------|
| 设备 | IMEI/MAC/蓝牙地址**必须全部唯一** |
| 网络 | 必须使用住宅 IP机房 IP 存活率 < 15% |
| 内容 | 严禁搬运必须原创或深度改写 |
| 行为 | 新号浏览笔记 > 30min/天,持续 3-7 天再操作 |
| 操作 | 单日私信 ≤ 50点赞 ≤ 300收藏 ≤ 200 |
### 3.4 闲鱼防封策略
| 维度 | 要求 |
|------|------|
| 账号 | 芝麻信用 > 600实名认证支付宝绑定 |
| 行为 | 先浏览 10-15 分钟再发私信 |
| 内容 | 私信避免"加微信"等引流词 |
| 频率 | 单日私信 ≤ 80发布 ≤ 10 |
### 3.5 小程序防封策略
微信小程序有独立的安全防护 API风险等级 0-4越高越危险
| 风险等级 | 含义 | 对策 |
|:--------:|------|------|
| 0 | 正常 | 正常操作 |
| 1 | 可疑 | 降低操作频率 50% |
| 2 | 异常 | 暂停操作 30 分钟 |
| 3 | 高危 | 暂停操作 2 小时,切换策略 |
| 4 | 确认异常 | 停止该账号操作,人工介入 |
小程序特有防封要点:
- 避免短时间大量调用同一小程序 API
- 模拟正常小程序使用流程(授权→浏览→操作)
- 不在小程序中直接发送营销内容
---
## 四、行为层防封(全局 — 全员遵守)
### 4.1 拟人化操作框架
**所有自动化操作必须通过防封行为层**,不得直接调用底层 API
| 行为要素 | 机器特征 | 人类特征 | 实现方式 |
|----------|----------|----------|----------|
| 操作间隔 | 固定值 | 高斯随机分布 | `human_delay()` |
| 触摸轨迹 | 精确直线/瞬移 | 贝塞尔曲线+抖动 | `human_swipe()` |
| 文字输入 | 瞬间完成 | 逐字+偶尔退格 | `human_type()` |
| 浏览行为 | 无 | 滑动+停留+返回 | `random_browse()` |
| 操作时段 | 24h 不间断 | 8:00-22:00 为主 | 调度策略 |
| 连续操作 | 无休息 | 30-60min 后休息 | 自动空闲 |
### 4.2 养号策略自动化
Agent 应内置自动养号功能,在非任务时段自动执行:
```
每日养号流程(新号期,可配置):
07:30 模拟闹钟唤醒,解锁手机
07:35 打开微信,浏览朋友圈 5min
07:45 看 3-5 条微信文章
08:00 发 1 条朋友圈(生活类)
09:00 刷抖音 15min
12:00 微信聊天(回复已有对话)
14:00 逛小红书 10min
18:00 发 1 条朋友圈
20:00 微信支付(小额)
22:00 设备休息(仅保持心跳)
```
### 4.3 内容防封策略
| 策略 | 说明 |
|------|------|
| **变量模板** | 使用 `{昵称}` `{时间}` `{地点}` 等变量,每次发送自动替换 |
| **同义词库** | 维护敏感词→安全词映射表,自动替换 |
| **零宽字符** | 在文本随机位置插入 `\u200b`(零宽空格),确保每条消息唯一 |
| **表情随机** | 在消息末尾随机添加 1-2 个表情 |
| **格式变化** | 同一意思用不同格式(纯文本/带图/语音) |
**敏感词黑名单**(定期更新):
- 微信加我、扫码、转账、红包、优惠、免费、赚钱、兼职
- 各平台最新封禁关键词(通过 API 动态拉取)
---
## 五、监控与应急(阿表+阿服负责)
### 5.1 风控监控仪表盘
需在管理端F9 风控中心)实现的监控指标:
| 指标 | 阈值 | 告警方式 |
|------|:----:|----------|
| 单设备操作成功率 | < 80% | 飞书/邮件告警 |
| 账号状态异常被限制/被封 | 任何一个 | 立即告警 + 停止该设备 |
| 单设备日操作量接近上限 | > 90% | 预警提醒 |
| IP 被封检测 | HTTP 403/429 | 自动切换 IP |
| Frida 连接断开 | 连续 3 次 | 重启 Agent |
| 设备指纹碰撞 | 任何一对 | 立即停止碰撞设备 |
### 5.2 应急处理流程
#### 账号被临时限制
```
1. 立即停止该账号所有自动化操作
2. 24小时内不操作
3. 手动完成 5 笔小额支付(< 1 元)
4. 手动发布 3 条生活朋友圈(带定位)
5. 等待 48 小时后逐步恢复(频率降低 50%
```
#### 账号被永久封号
```
1. 72小时内通过微信安全中心申诉
2. 准备材料:身份证正反面、手持身份证照、聊天记录、支付账单
3. 若有企业资质,提交企业申诉(成功率 40-64%
4. 回溯封号原因 → 更新防封策略 → 记录到经验库
```
### 5.3 封号归因分析
每次封号必须做归因分析并更新本文档:
| 分析维度 | 检查项 |
|----------|--------|
| 操作日志回溯 | 封号前 24h 的所有操作记录 |
| 频率检查 | 是否超过平台限制 |
| 内容检查 | 是否触发敏感词 |
| 设备检查 | 指纹是否碰撞/Root是否暴露 |
| 网络检查 | IP 是否被标记 |
| 关联检查 | 是否与其他封号账号有关联 |
---
## 六、GitHub 开源项目索引
| 项目 | 地址 | 用途 | 对应人 |
|------|------|------|--------|
| **Phantom-Frida** | github.com/TheQmaks/phantom-frida | Frida反检测16向量全覆盖 | 阿机 |
| **StrongR-Frida** | github.com/hzzheyang/strongR-frida-android | Frida反检测社区活跃 | 阿机 |
| **Florida** | github.com/Ylarod/Florida | Frida基础反检测 | 阿机 |
| **DeviceSpoofLab** | github.com/yubunus/DeviceSpoofLab-Hooks | 设备指纹伪装126+属性) | 阿服 |
| **Build-var-Spoof** | github.com/LSPosed/Build-var-Spoof | Build属性伪装 | 阿服 |
| **Anti-EmuDetector** | github.com/u0pattern/Anti-EmuDetector | 模拟器检测绕过 | 阿机 |
| **FridaAntiRootDetection** | github.com/AshenOneYe/FridaAntiRootDetection | Root检测绕过 | 阿机 |
| **fake-android** | github.com/kkoooqq/fake-android | 多设备群控防检测 | 阿机 |
| **Shamiko** | magisk.dev/modules/shamiko | Root隐藏模块 | 阿服 |
| **HMA-OSS** | github.com/Dr-TSNG/Hide-My-Applist | 应用列表隐藏 | 阿服 |
---
## 七、开发 Checklist每次开发前核验
**任何涉及设备操作的代码修改,必须逐项检查**
- [ ] 操作是否经过 `RateLimiter` 频率检查?
- [ ] 操作间隔是否使用 `human_delay()` 随机延迟?
- [ ] 文字输入是否使用 `human_type()` 逐字输入?
- [ ] 滑动操作是否使用 `human_swipe()` 贝塞尔曲线?
- [ ] 消息内容是否经过 `diversify_content()` 差异化?
- [ ] 是否检查了日操作上限?
- [ ] 新号是否走了养号策略限制?
- [ ] 是否有敏感词过滤?
- [ ] Frida Server 是否使用反检测版本?
- [ ] 设备指纹是否唯一(无碰撞)?
- [ ] Root 隐藏是否配置完毕?
- [ ] 操作时段是否在合理范围?
- [ ] 是否有操作后的"自然行为"穿插?
---
## 八、版本更新日志
| 日期 | 版本 | 更新内容 |
|------|:----:|----------|
| 2026-03-14 | v1.0.0 | 初版:四层防封策略(设备/服务器/应用/行为)+ GitHub项目索引 + 开发Checklist |
> **持续更新机制**:每次封号事件 → 归因分析 → 更新本文档对应章节 → 通知全员。
> **季度审查**:每季度搜索全网最新防封策略,更新平台检测规则与绕过方案。