简介:基于C#的微信自动化模拟工具源码包,面向有一定C#基础的开发者及对桌面自动化感兴趣的测试人员。项目模拟微信客户端行为,使用户无需直接登录即可实现自动回复、联系人管理等操作,可用于个人效率工具、社交软件自动化研究或C#桌面开发练习。压缩包共55个文件,以C#源代码(cs)、JSON配置、DLL依赖库、EXE可执行为主,另有工程配置文件、Markdown文档与PDB调试符号,整体仅2.21MB,目录清晰适合快速定位。目前已有676人学习/下载,适合关注WinForms项目结构、消息模拟原理和轻量级自动化流程的开发者参考,也适合用于毕业设计、课程项目或个人脚本开发。从内容预览可见,资源包含完整解决方案、窗体界面代码、程序入口与说明文档,并附有授权文件;使用者能获得一套可运行、可改造的源码骨架,在界面编写、事件触发、资源文件管理及程序集引用等工程实践方面获得具体参考。
1. 基于C#的微信自动化模拟工具:先弄懂它解决的到底是什么问题
做惯了C#上位机或桌面工具开发的工程师,多半都遇到过这类需求:每天定时给固定联系人推送巡检结果、给群发周报、或者把某个业务系统的告警转成微信消息。手工复制粘贴一两次能忍,上百次就是纯耗命。标题里的「基于C#的微信自动化模拟工具」解决的就是这件事:读取窗口句柄、定位输入框、模拟键盘与消息,把「打开微信→找到会话→输入文本→按回车」这串动作用C#代码接管。它不碰微信的数据库文件,不走协议层,也不注入进程,本质是站在UI层的自动化模拟,和Appium、Playwright做UI自动化的思路同源,只是目标程序换成了桌面微信。
这套方案适合谁,一句话讲清:适合需要在Windows桌面微信上做批量、定时、可追踪操作的C#开发者。反直觉的结论先放这儿——写「发送」的代码只需要半小时,真正让工具活过三个月的是发送确认、版本锁定和异常排查。下面从原理讲到落地。
2. 模拟输入为什么选C#和Win32消息,而不是Hook或协议破解
动手之前先把选型理由讲清楚。微信自动化不止一条路,但每条路的代价差别非常大,选错方向会把整个项目拖进泥潭。
2.1 微信窗口对自动化工具开放了什么:句柄、控件树与消息
Windows桌面应用的自动化基础是Win32窗口体系。微信虽然界面越来越像网页,但跑在Windows上终究是一个或多个顶层窗口(hWnd),窗口内部还挂着一批子窗口——输入区、搜索框、会话列表、标题栏,这些子窗口都有独立的句柄和类名。发送一条微信消息,本质就是向输入框所在窗口投递「填入文本」和「按下回车」两条指令。
这套机制对自动化工具是友好的:EnumWindows可以枚举桌面所有顶层窗口,EnumChildWindows可以遍历一个窗口内部的子控件,SendMessage和PostMessage可以向指定窗口发送文本或按键消息。C#通过P/Invoke调用user32.dll,写起来和C++几乎一样快,却省掉了内存管理和指针的负担,开发效率明显更高。
唯一要清楚的是,微信的新版本(4.x尤其明显)大量使用自绘和CEF渲染,输入框不一定还是标准Edit控件,类名可能是WeChatWidgetWin这类宿主窗口。但这不改变自动化策略,只是让「控件识别」这一层需要更多的适配与容错。
2.2 三条主流实现路线:SendMessage最稳、注入Hook最险
从业界常见做法看,微信自动化有三条路线,风险从低到高排列如下。
| 路线 | 实现方式 | 稳定性 | 风险与代价 | 适用场景 |
|---|---|---|---|---|
| UI消息模拟 | EnumWindows找句柄 + SendMessage/PostMessage + 按键模拟 | 最稳,微信升级前可持续运行 | 受控件结构变化影响 | 批量发送、定时推送、日常自动化 |
| UI Automation | 通过FlaUI/UIAutomation读取控件树,调用ValuePattern | 较稳,适配性比裸消息好 | 需要额外依赖,x64下要处理配置 | 控件识别困难、需要读取状态时 |
| Hook注入 | SetWindowsHookEx挂键盘/消息钩子,或DLL注入 | 能监听更多事件,但崩得也快 | 回调线程不归你管,C#委托被GC回收会直接Access Violation | 需要监听全局输入时才考虑 |
| 协议/数据库方案 | 解密本地数据库、逆向通信协议 | 功能最强,但工作量和风险都最高 | 涉及加密密钥,且依赖版本强 | 不建议普通团队碰 |
我的结论很直接:做「模拟工具」就用第一或第二条路线,第三条Hook用于监听辅助,尽量不要用它发消息。SendMessage不注入进程,不碰内存,出问题最坏是消息没送达,不会把微信搞崩。Hook看着强大,但回调运行在微信的线程上下文里,你在这个回调里做任何窗口操作都不可控,C#里还额外背着委托生命周期的雷,这就是网上大量Access Violation c0000005报错的来源。
2.3 C#侧怎么选库:user32 P/Invoke与FlaUI的分工
C#实现这套方案,最朴素的路径是不引第三方库,直接用DllImport声明user32.dll的函数。优点是没有依赖、发布体积小、逻辑透明,出问题时你能确认每一步在干什么。缺点是你要自己处理委托生命周期、字符串编组和句柄释放,但这些对于一个桌面自动化工具来说都是可控的。
我会把活儿拆成两层:底层窗口操作(枚举句柄、发送消息)用P/Invoke,控件识别这层优先用UI Automation。FlaUI是社区里封装得比较成熟的UIAutomation库,适合读控件树、找搜索框、判断输入框状态。两者分工的好处是,底层消息发送稳定且不依赖微信的具体控件结构,上层控件识别有标准化的接口兜底,微信版本再变也能撑一段时间。
动手前先装一个Spy++,它是看窗口类名和控件树的核心工具。所有类名、标题判断都要以Spy++实际看到的为准,千万别凭感觉硬编码。
3. 跑通最小可用版:用C#定位微信窗口并自动发送第一条消息
目标先定小:打开微信,自动给某个联系人发一句话。这一段跑通了,整个工具的地基就有了。
3.1 按进程名找主窗口:EnumWindows与窗口标题过滤
第一件事是在桌面窗口里找到微信主窗口。常见做法是先用进程名过滤,再通过窗口可见性和标题做二次确认,避免抓到隐藏的辅助窗口。
using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; public static class WeChatWindow { [DllImport("user32.dll")] private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); [DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr hWnd); private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); public static IntPtr FindMainWindow(string processName = "WeChat") { Process[] procs = Process.GetProcessesByName(processName); if (procs.Length == 0) return IntPtr.Zero; int targetPid = procs[0].Id; IntPtr result = IntPtr.Zero; EnumWindows((hWnd, lParam) => { GetWindowThreadProcessId(hWnd, out uint windowPid); if (windowPid != targetPid) return true; // 不是微信进程,跳过 if (!IsWindowVisible(hWnd)) return true; // 隐藏窗口不参与匹配 var sb = new StringBuilder(256); GetWindowText(hWnd, sb, sb.Capacity); if (sb.ToString().Contains("微信")) { result = hWnd; return false; // 找到即停 } return true; }, IntPtr.Zero); return result; } }这段代码里有三个参数要注意。processName默认设为"WeChat",这是微信主进程的名字,如果机器上运行的是微信4.x,进程名可能带版本后缀,先在任务管理器里确认再传参。EnumWindowsProc回调返回true表示继续枚举,返回false表示停止,找到主窗口后马上中断,避免遍历全桌面的开销。GetWindowText需要CharSet.Unicode,微信窗口标题是UTF-16字符串,编组方式不对会读出乱码。
如果你的微信主窗口标题改过(设置里可以改「聊天窗口显示的名字」),Contains("微信")这套判断会失效。更稳的方式是用进程ID直接比对窗口PID,标题只做辅助条件。
3.2 递归查找输入框控件:EnumChildWindows与类名判断
拿到主窗口句柄后,下一步是在它的子窗口里找消息输入框。微信的输入区在3.x和4.x版本里结构不同,直接递归枚举所有子窗口,按类名过滤即可。
[DllImport("user32.dll")] private static extern bool EnumChildWindows(IntPtr hWndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); public static IntPtr FindChatInput(IntPtr mainWindow) { IntPtr found = IntPtr.Zero; EnumChildWindows(mainWindow, (child, lParam) => { var cls = new StringBuilder(128); GetClassName(child, cls, cls.Capacity); string className = cls.ToString(); // 3.x常见Edit/RichEdit,4.x可能变成宿主窗口,需用Spy++确认 if (className.Contains("Edit") || className.Contains("RichEdit")) { found = child; return false; } return true; }, IntPtr.Zero); return found; }这里的类名判断是整个工具里最容易翻车的地方。不同微信版本的输入框类名词表完全不一致:有的版本是RichEdit50W,有的版本是DirectUIHWND,4.x的CEF渲染下可能根本找不到带Edit字样的窗口。我一般会把类名列表抽出来做成配置项,先在Spy++里抓到当前版本的输入框类名,再填进配置。另外注意,如果微信窗口处于折叠状态或者聊天区域未激活,子窗口可能尚未创建,这种情况下先激活主窗口,再等几百毫秒重新查找。
3.3 模拟填充与发送:WM_SETTEXT和回车键的参数细节
控件找到了,就进入真正的发送环节。发送分为两步:向输入框写入文本,模拟按回车。这里有一个经验点:先激活主窗口再发消息,否则消息可能被微信忽略。
using System.Threading; public static class Sender { [DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hWnd); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, string lParam); [DllImport("user32.dll")] private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); private const uint WM_SETTEXT = 0x000C; private const uint WM_KEYDOWN = 0x0100; private const uint WM_KEYUP = 0x0101; private const uint VK_RETURN = 0x0D; public static void SendTextLine(IntPtr mainWindow, IntPtr inputHandle, string text) { SetForegroundWindow(mainWindow); Thread.Sleep(200); // 写入前清空输入框,避免追加到上一段未发送的草稿 SendMessage(inputHandle, WM_SETTEXT, IntPtr.Zero, string.Empty); SendMessage(inputHandle, WM_SETTEXT, IntPtr.Zero, text); // 模拟回车触发发送 SendMessage(inputHandle, WM_KEYDOWN, (IntPtr)VK_RETURN, IntPtr.Zero); SendMessage(inputHandle, WM_KEYUP, (IntPtr)VK_RETURN, IntPtr.Zero); } }两个重载的SendMessage区别在最后一个参数:一个是字符串,一个是IntPtr。写入文本用字符串版本,模拟按键用IntPtr版本,C#重载解析会自己选,但你要保证调用时类型写对。WM_SETTEXT是0x000C,作用是把输入框内容整体替换成给定文本;WM_KEYDOWN和WM_KEYUP配套发送,单独发DOWN或者单独发UP可能会导致按键状态卡住。
这里有个必须知道的边界:如果文案里带换行,微信默认会把回车当成发送而不是换行。多行文本在UI模拟层面没有干净的解法,常见做法是把长文案拆成多条短消息逐条发送,这也更贴近人工操作的习惯。先在小号上测试几轮再上真实账户,至少确认消息真的发出去了再谈批量。
4. 从单条消息升级成可用工具:会话切换、发送确认与频率控制
能发一条,就能发一批。但批量与单条之间差着三个硬问题:怎么切会话、怎么确认发送成功、怎么控制节奏。这一章把它们逐个落地。
4.1 搜索会话并切入输入框:从列表项到输入框的完整链路
批量发送最常见的场景是「给不同联系人发不同的消息」,所以每次发送前都要切换会话。人工操作是点左侧搜索框输入备注再点结果,自动化也照做:模拟Ctrl+F聚焦搜索,写入联系人名称,等待搜索结果出现,按下回车进入会话。
public static void SwitchChat(IntPtr mainWindow, string contactName) { // Ctrl+F 聚焦微信搜索框 SendShortcut(mainWindow, 0x01 /* VK_LCONTROL */, 0x46 /* 'F' */); Thread.Sleep(300); IntPtr searchBox = FindSearchBox(mainWindow); if (searchBox == IntPtr.Zero) return; // 写入联系人备注名,微信搜索是异步的 SendMessage(searchBox, WM_SETTEXT, IntPtr.Zero, contactName); Thread.Sleep(800); // 第一项结果通常是目标会话,回车进入 SendMessage(mainWindow, WM_KEYDOWN, (IntPtr)VK_RETURN, IntPtr.Zero); SendMessage(mainWindow, WM_KEYUP, (IntPtr)VK_RETURN, IntPtr.Zero); Thread.Sleep(300); }这段逻辑里有三个陷阱。第一,搜索结果是异步渲染的,写完后不能立刻按回车,至少要等500到1000毫秒;更稳的做法是轮询搜索框下方的结果列表是否有可见项。第二,SearchBox的查找同样依赖版本类名,3.x是独立的搜索Edit控件,4.x可能需要走UI Automation去找。第三,如果联系人备注重复,按回车会进入第一个结果,这可能不是你要的那个会话——这种场景我一般会改成先读取结果列表的前几项文本,匹配上了再发送。
4.2 发送确认:怎么判断消息真的发出去了而不是只填进了输入框
这是整个工具里最容易被忽略、也最致命的环节。很多初版工具只做了「写入文本+按回车」两步,但回车发没发出去完全无感知:输入框不接收文本、回车被当成换行、会话没切换成功,任何一种情况都会让消息静静地躺在草稿框里,而你的程序还记着「发送成功」。
一个可行的发送确认逻辑是组合信号:发完回车后,轮询输入框的文本是否已被清空,同时检查会话窗口标题是否仍指向目标联系人。
public static bool ConfirmSent(IntPtr mainWindow, string contactName, int retryTimes = 5) { for (int i = 0; i < retryTimes; i++) { IntPtr input = FindChatInput(mainWindow); if (input == IntPtr.Zero) { Thread.Sleep(200); continue; } string text = GetWindowText(input); string title = GetWindowTitle(mainWindow); // 微信发送成功后输入框会被清空,标题仍保留联系人名 if (string.IsNullOrWhiteSpace(text) && title.Contains(contactName)) return true; Thread.Sleep(200); } return false; }这里的判断逻辑基于微信的行为特性:发送成功后输入框内容被清空,这是最朴素的成功信号。但要注意边界情况,消息太长发送失败时输入框也会被清空,所以还要加一条「标题仍指向目标会话」做二次确认。更严格的项目会把聊天区域的消息数量也纳入验证——发送前记录气泡数,发送后数量加一才算成功。确认机制越重,误报越少,代价是每次发送多花几百毫秒。批量场景建议至少保留输入框清空+标题双确认。
4.3 频率控制与多账号:把批量发送做成低风险的定时任务
确认机制解决了「发没发出去」,频率控制解决的是「发出去之后会不会触发风险」。微信对消息频次和操作节奏有行为异常判断,固定间隔均匀点击反而更像机器。常见做法是把每条消息的间隔设成一个随机范围,同时把每轮任务的上限、暂停、继续的条件都写清楚。
public class SendScheduler { private readonly Random _rand = new Random(); private readonly int _minIntervalMs; private readonly int _maxIntervalMs; public SendScheduler(int minIntervalMs = 800, int maxIntervalMs = 1500) { _minIntervalMs = minIntervalMs; _maxIntervalMs = maxIntervalMs; } public void Run(List<(string Contact, string Message)> tasks, IEnumerable<string> recipients) { foreach (var task in tasks) { foreach (var contact in recipients) { SwitchChat(MainWindow, contact); IntPtr input = FindChatInput(MainWindow); if (input == IntPtr.Zero) continue; SendTextLine(MainWindow, input, task.Message); bool ok = ConfirmSent(MainWindow, contact); // 随机间隔,避免固定节奏被识别 Thread.Sleep(_rand.Next(_minIntervalMs, _maxIntervalMs)); if (!ok) { // 发送失败立刻停,不要闷头继续 throw new InvalidOperationException($"发送失败: {contact}"); } } } } }参数设置上,我一般把单条间隔放在800到1500毫秒之间,这是一段接近人工手速的窗口;如果你要发的是长通知,把间隔上限提高到3000毫秒。多账号场景不要共享同一个Scheduler实例,每个账号一个调度器、一套独立的间隔预算,避免账号间操作互相影响。发送失败立刻抛异常而不是continue,也是我的习惯——批量工具一旦静默失败,两个小时后你看到的是一堆半截任务,而不是一个清晰的中断点。
5. 微信自动化的五个高频坑:现象、原因与排查顺序
前面把主链路跑通了,这一章写我在这个方向上反复踩过的坑。每一条都按「现象→原因→解决」来写,你可以把它当排查手册用。
5.1 输入框句柄拿到了,SendMessage却没有反应
现象:FindChatInput返回了句柄,WM_SETTEXT调用也返回了非零值,但微信界面上什么都没出现。
原因:这个句柄多半不是真正接收文本的控件。微信3.x后期和4.x的输入区是CEF/自绘渲染,句柄存在但消息进入渲染进程后被丢弃,WM_SETTEXT自然不生效。另一个常见原因是窗口处于非激活状态,消息被忽略。
解决:先SetForegroundWindow激活主窗口,等200毫秒再发消息;如果仍然无效,切换路线,用UI Automation的ValuePattern直接设置输入框值。ValuePattern不依赖消息循环,对自绘控件适配更好。
// FlaUI方案:绕开WM_SETTEXT,改用UIA的ValuePattern using FlaUI.UIA3; var app = FlaUI.Core.Application.Attach(processId); using (var automation = new UIA3Automation()) { var window = app.GetMainWindow(automation); var input = window.FindFirstDescendant(cf => cf.ByClassName("Edit")); var pattern = input.AsValuePattern(); pattern.Value = text; // 直接赋值,不经过消息机制 }这套方案的代码量更少,但代价是要引入FlaUI依赖,并且进程需要以UI Automation兼容方式运行。我的建议是主链路保留P/Invoke方案,ValuePattern作为降级分支,运行期如果发现控件类名匹配不上就自动切换,比写死一种方案耐折腾。
5.2 控件树偶发为空:异步渲染下的黑匣子重试策略
现象:工具刚启动时一切正常,连续运行半小时后,FindChatInput开始返回IntPtr.Zero,或者找到的句柄操作无效。
原因:微信的界面在切换会话、网络重连、内存紧张时会重建渲染子树,重建期间子窗口暂时不存在。这不是程序bug,是目标程序自身的异步行为。
解决:对所有控件查找加可配置重试。重试间隔用退避策略,先短后长,避免高频空转。
public static IntPtr FindInputWithRetry(IntPtr mainWindow, int retries = 5) { for (int i = 0; i < retries; i++) { IntPtr h = FindChatInput(mainWindow); if (h != IntPtr.Zero) return h; Thread.Sleep(200 * (i + 1)); // 200/400/600/800/1000ms } return IntPtr.Zero; }另外一个玄学但真实的现象:长时间运行后句柄虽然存在,但消息发出去不生效,这种情况直接丢弃旧句柄重新枚举。不要在回调里做任何耗时操作,EnumChildWindows是同步枚举,你拖的时间越长,微信内部的状态变化越不可控。
5.3 注入Hook后进程崩溃报Access Violation (c0000005)
现象:用SetWindowsHookEx挂键盘钩子来监听消息,挂钩后微信频繁崩溃,事件日志里是c0000005。网上搜Access Violation c0000005能找到大量同类求助,但解法几乎都一样。
原因:C#委托被GC回收后,Hook回调变成了野指针;或者在Hook回调里直接操作了窗口和UI。回调线程属于微信的进程上下文,不是你的UI线程,在这个线程里调SendMessage经常触发重入导致访问违规。
解决:第一,把委托实例用static字段持有,生命周期等同于进程,禁止局部变量裸传;第二,回调里只做入队或计数,所有窗口操作交给你自己的线程统一处理。
private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam); // 静态字段持有委托,防止被GC回收 private static readonly HookProc _hookHandler = HookCallback; private static readonly ConcurrentQueue<HookEvent> _eventQueue = new(); private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) { // 只入队,不做任何窗口操作,不调SendMessage _eventQueue.Enqueue(new HookEvent(nCode, wParam, lParam)); return CallNextHookEx(_hookId, nCode, wParam, lParam); }记住这条铁律:Hook回调不是你自己的领地,它是借来的线程。所有UI操作都回到主线程执行,否则c0000005只是第一个信号,后面还会有死锁和随机崩溃。
5.4 电脑微信升级后控件结构大改,工具一夜失效
现象:某天工具突然找不到输入框,Spy++一看类名全变了,界面上原来能识别的控件全没了。
原因:微信的UI框架在3.x到4.x之间发生过一次大换血,自绘控件和CEF渲染交替出现。升级前你硬编码的类名、窗口标题、搜索快捷键可能全部失效。
解决:版本锁定是第一道防线。关闭微信的自动更新,把当前可用的安装包存在本地当「后悔药」,工具和微信版本绑定使用。代码层面,把所有类名、标题关键词、窗口层级做成版本指纹配置,启动时先校验当前微信版本与工具配置的指纹是否匹配,不匹配就拒绝运行,别拿生产环境当测试机。
public class WeChatProfile { public string Version { get; set; } public List<string> InputClassNames { get; set; } public string MainWindowTitleKeyword { get; set; } } // 工具启动时校验:指纹不匹配立刻停止,并打印差异 public static bool CheckProfile(WeChatProfile expected, IntPtr mainWindow) { string title = GetWindowTitle(mainWindow); if (!title.Contains(expected.MainWindowTitleKeyword)) { Console.WriteLine($"[版本不匹配] 期望标题: {expected.MainWindowTitleKeyword}, 实际: {title}"); return false; } return true; }这个习惯救过我很多次。没有版本指纹的工具,升级后往往是半死不活地静默失败——找不到控件返回空,程序还在继续跑,日志里全是失败的记录,排查半天才发现根源是版本变了。
5.5 多开与屏幕缩放导致句柄错位、点击偏移
现象:多开微信时,消息发进了错误的窗口;高分屏或调整缩放比后,按坐标点击按钮全部偏移。
原因:多开时进程有多个PID,按第一个进程匹配必然错位;而WM_SETTEXT这类消息操作不受坐标影响,但一旦你使用鼠标点击(比如点发送按钮),DPI缩放后逻辑坐标和物理坐标换算错误就会偏。
解决:每次操作实时枚举进程和窗口,禁止在程序启动时缓存句柄。所有窗口匹配都以「进程PID+窗口可见性+标题」三条件同时满足为准。涉及坐标点击的地方,先调用SetProcessDPIAware再取坐标,或者干脆避免坐标点击,优先走控件消息。DPI这层坑一旦踩进去,症状是偶发性的,时好时坏,最让人头疼。
6. 让它稳定跑几个月:版本锁定、结构化日志与验证手段
工具跑通了,坑也排了,最后一步是让它在你睡觉的时候自己活着。我的做法是三件事:结构化日志、早晨自检、版本锁定。
结构化日志是排查的第一工具。每条发送记录一行,包含时间、PID、目标联系人、输入框句柄、发送结果、耗时、异常信息。文本文件就行,按天切割,出问题直接拖进Excel筛选。
public void LogSend(string contact, bool ok, string note, long elapsedMs) { var line = string.Format("{0:yyyy-MM-dd HH:mm:ss}\tPID[{1}]\t{2}\t{3}\t{4}ms\t{5}", DateTime.Now, _processId, contact, ok ? "OK" : "FAIL", elapsedMs, note); File.AppendAllText(_logPath, line + Environment.NewLine, Encoding.UTF8); }日志的价值在批量场景里会被放大:100条发送里出现3条失败,没有日志你只能重新跑一遍,有日志你能看出失败是否集中在某个联系人、是否集中在某个时间窗口。这也是我把发送确认做进主链路的原因——错误不可怕,静默的错误才可怕。
早晨自检是另一个偏低成本高收益的习惯。每天第一次启动工具时,往「文件传输助手」发一条测试消息,能收到回执说明UI链路还是通的。如果连续两天自检失败,基本可以判断微信版本变了或者界面结构动过,这时候看日志比看代码更快。
版本锁定要落实到操作:在微信设置里关闭自动更新,同时把当前可用版本的安装包备份到固定目录。我的教训是——曾经因为一个版本的升级,整个工具静默失效两天,日志里全是找不到控件的记录,最后排查发现是输入框类名变了。从那以后,我先锁版本,再谈自动化;每次改动代码也只动一个变量,跑一天观察日志确认稳定后,再动下一个。这套工具值不值得做,取决于你每天要处理多少条消息:次数过百,手工粘贴的时间成本已经超过了开发成本;次数只有几次,建议直接用电脑微信的原生功能,别给自己找活干。希望帮到你。
本文还有配套的精品资源,点击获取