ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Android验证码收不到?手把手教你从广播监听、解析到表格展示全链路实现

Android验证码收不到?手把手教你从广播监听、解析到表格展示全链路实现

验证码这种玩意,平时看着不起眼,真到做项目、调试接口、跑自动化测试的时候,就成了卡脖子的东西。尤其做 Android 开发,App 里收验证码、填验证码、验验证码,一套流程下来,手机上短信箱早就被各种通知淹没了。想找一条十分钟前的验证码,翻半天还不一定找得到,更别提一边在电脑上改代码、一边还要拿起手机看短信这种操作有多打断节奏。

我之前在做一个内部测试用的跨平台项目时,就深受其害。每次联调都要跟测试同学在群里互相喊“验证码发了吗?是多少?”,效率低得离谱。后来我干脆在 Android 端做了一个“专门的验证码表”,把接收、解析、存储、展示、复制这一整条链路打通,用起来是真的爽。这篇文章就把我从零到一实现这个验证码表的完整思路、核心代码、踩坑记录都捋一遍,给有同样需求的朋友做个参考。

1. 内容整体设计与思路拆解

1.1 核心需求:到底什么场景需要“验证码表”

先说清楚,这个“验证码表”不是给普通用户做短信验证码收集的,而是面向开发者、测试人员、以及需要频繁处理验证码的场景。

我当时的真实需求有几点:

  • 能实时接收 App 注册、登录、找回密码等流程下发的短信验证码。
  • 自动从短信内容里把纯数字或字母数字混合的验证码提取出来。
  • 生成一张结构化的表格,按时间倒序排列,一眼能看到最近收到的验证码。
  • 点击某条记录就能复制验证码到剪贴板,省得手动记。
  • 历史记录要能存下来,测试环境经常同一手机号反复收码,翻旧账是常事。

如果你也在做账号体系联调、爬虫验证、或者自动化测试脚本,这个表就是个超级好用的辅助工具。它跟普通短信 App 最大的区别在于:普通短信 App 是“读消息”,这个表是“提取关键信息并管理关键信息”。

有些人可能会问,直接用系统短信不好吗?一是短信列表被各种营销信息污染,二是验证码和上下文文案混在一起,跟接口联调时根本没法快速定位。专门做一张表,等于是给验证码单独建了个索引,效率完全不在一个量级。

1.2 方案选型:为什么用广播接收器 + SQLite + RecyclerView

这个项目的技术选型,我前后对比了好几条路。

第一版我想得非常简单:直接用系统自带的短信数据库,读取telephony的content://sms然后筛选。但问题很明显:从 Android 4.4 开始短信权限就收紧了,普通应用读取短信数据库需要READ_SMS权限,而且在高版本系统上这种常驻读取行为很容易被系统标记为“滥用权限”,而且每次读数据库都慢,不符合“实时”的需求。

第二版我考虑过用第三方云平台做短信转发,比如把验证码转发到服务端再回调。但这引入了网络依赖,而且我本来就是要做一个纯客户端小工具,没必要绕这么大一圈。

最终敲定的方案是:

  • 监听系统短信广播android.provider.Telephony.SMS_RECEIVED,收到短信后立即在广播里解析。
  • 用 SQLite 做本地存储,记录验证码、发送方号码、时间、短信原文。
  • 用 RecyclerView 做列表展示,外加一个搜索框和复制按钮。
  • 用广播或 LiveData 通知界面刷新。

这个方案的好处在于:

  • 响应实时,短信到了就是到了,App 在后台也能立刻处理。
  • 不依赖网络,纯本地逻辑,数据安全也好控制。
  • 结构清晰,后续想加筛选、加导出都很容易。

一句话总结:本地监听 + 本地存储 + 列表展示,就是验证码表最务实的架构。

1.3 权限与合规:这块的坑必须提前说

开发这类工具最容易翻车的就是权限和合规。下面这些点,建议你开工前就理清:

  • RECEIVE_SMS是危险权限,必须动态申请。Android 6.0 之后不动态申请直接崩溃,没有任何商量余地。
  • READ_SMS权限不是必选的,但如果你要读取手机里已有的历史短信来补录验证码,那就需要。我实际做的时候果断放弃了历史短信补录,只处理“从今往后”收到的验证码,权限只申请RECEIVE_SMS,省了一大堆隐私合规问题。
  • 如果你的 App 是面向普通用户上架应用商店的,短信权限是敏感权限,用途说明必须写清楚,而且绝大多数应用商店(尤其是海外渠道)对短信权限的审核极其严格。我这个表是内部测试工具,不属于公开分发场景,所以走得通。
  • 如果目标设备是 Android 13 及以上,部分厂商对短信广播的限制会更狠,实测下来有些定制 ROM 需要手动授予“常驻通知”或“后台运行”权限,否则收不到广播。

另一个必须拎清楚的点:这个工具不能做成恶意收集用户短信的“短信窃听器”。我自己的使用场景就是测试手机 + 测试号码,所有验证码都是自己触发的,本质上像个“测试辅助仪器”。做工具可以,做黑产就是另一回事了,这个度得把握好。

2. 核心细节解析与实操要点

2.1 短信广播接收器:不是光注册就能收到

实现实时接收的关键,就是写一个继承自BroadcastReceiver的类,然后在AndroidManifest.xml里声明接收SMS_RECEIVED广播。

广播接收器的基础代码长这样:

public class SmsReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction() != null && intent.getAction().equals(Telephony.Sms.Intents.SMS_RECEIVED_ACTION)) { // 处理收到的短信 } } }

需要注意的细节非常多:

  • 广播是不允许做耗时操作的,比如网络请求、复杂数据库写入。我实测下来,广播里直接批量写入 SQLite 偶尔会 ANR,尤其短信密集到达的时候。正确做法是广播里只做解析和数据的轻量转发,比如发个LocalBroadcastManager或者LiveData给后台线程去落库。
  • 多个短信会被塞在同一个 Intent 里,要用Telephony.Sms.Intents.getMessagesFromIntent(intent)解析出来,不然一条多段短信只取到了第一段。
  • 有些定制 ROM 对SMS_RECEIVED广播做了限制,尤其是电池优化、后台限制方面。我测试用的某国产手机上,第一次装上跑得好好的,过两天就没反应了,后来才发现是省电策略把应用的后台广播给掐了。

针对低版本和高版本,还有一个细节差异:Android 8.0 之后,静态注册的广播接收器对隐式广播有限制,但SMS_RECEIVED是例外,它仍然允许静态注册。所以这块不用额外加setComponent之类的操作。

2.2 验证码解析:正则只是入门,异常情况才是大头

验证码提取,很多人第一反应就是写个\d{4,6}正则完事。实际做下来会发现,这玩意儿的坑比想象中多得多。

我整理常见的验证码短信格式大概有这么几类:

  • 纯数字验证码:如“您的验证码是123456,请勿泄露。”
  • 数字+字母混合:如“验证码 Ab3fG9 有效期为5分钟。”
  • 带标点分割:如“验证码:123-456,请输入。”
  • 英文场景:如“Your verification code is 123456. Do not share it.”
  • 特殊平台格式:如“【某公司】验证码为5678,工作人员不会索要。”

所以正则不能只写一个死板模板,而是要分层处理。

经验做法是:

  1. 先用关键词定位验证码附近的位置,比如“验证码”“code”“کد(波斯语?虽然少见,但国际化要考虑)”。
  2. 在关键词附近截取一段文本,比如关键词后 20 个字符以内。
  3. 对截取出来的文本,去掉常见噪音符号-、空格、冒号、中文全角符号。
  4. 提取连续的数字或数字字母串,优先匹配较长的那一段。
  5. 如果还是提取不到,回退到全文匹配 4~8 位纯数字或 6 位数字字母组合。

我这边的解析代码大概长这样:

public class CodeParser { private static final Pattern PATTERN_DIGIT = Pattern.compile("(\\d{4,8})"); private static final Pattern PATTERN_ALNUM = Pattern.compile("([A-Za-z0-9]{4,8})"); public static String parse(String message) { if (message == null || message.isEmpty()) { return ""; } // 第一优先:验证码等关键词附近捕获 String[] keywords = {"验证码", "校验码", "verification code", "code"}; for (String keyword : keywords) { int idx = message.toLowerCase().indexOf(keyword.toLowerCase()); if (idx >= 0) { String window = message.substring(idx, Math.min(message.length(), idx + 30)); String cleaned = window.replaceAll("[-\\s::,,。;;]", ""); Matcher m = PATTERN_ALNUM.matcher(cleaned); if (m.find()) { String candidate = m.group(); // 过滤掉明显不是验证码的长串,比如链接 if (candidate.length() >= 4 && candidate.length() <= 8) { return candidate; } } } } // 第二优先:全文提取数字 Matcher m = PATTERN_DIGIT.matcher(message); if (m.find()) { return m.group(); } return ""; } }

这里有几个坑,我实际填了很久:

  • 链接干扰:短信里经常会带“点击链接 http://xxx”。链接里的数字如果正好是四到八位,容易被误提取。我最后的处理办法是在解析前先剔除 URL,把http://、https://开头的连续非空格字符整体拿掉。
  • 验证码里带横杠:以前遇到过“123-456”这种,直接把横杠去掉再匹配,稳妥。
  • 中英文标点混用:中文短信里经常出现全角冒号、全角逗号,建议replaceAll("[\\p{Punct}\\s]", ""),把标点全清掉再提取。
  • 语音验证码:这个是硬伤,语音验证码收不到短信广播,无解。后来我实测发现某些平台的语音验证码其实也会附带一条文本短信,所以这种场景不强求。

2.3 数据存储:SQLite 的选型和表结构设计

验证码表的核心存储,我没有引入 Room 或者 GreenDAO 这类重量级 ORM,直接用原生 SQLite。原因很简单:这个表就一张表,数据量撑死几千条,功能就插入、查询、删除,用 ORM 属于杀鸡用牛刀,而且会引入一堆依赖。

我的表结构设计如下:

CREATE TABLE verify_code ( id INTEGER PRIMARY KEY AUTOINCREMENT, code TEXT NOT NULL, phone TEXT, message TEXT, received_time INTEGER NOT NULL );
  • code:解析出来的验证码,必须非空。
  • phone:发送方号码,有可能是营销号段,也可能是服务号码。
  • message:短信原文,留着方便回看上下文。
  • received_time:接收时间戳,毫秒为单位,排序和展示都用它。

加索引的话,我会在received_time上建个索引,因为主查询就是按时间倒序排列:

CREATE INDEX idx_time ON verify_code(received_time);

数据库操作这里我直接用SQLiteOpenHelper管理,简单可靠。需要强调的是,数据库的写操作不能在主线程执行。我封装了一个CodeRepository,内部用单线程的ExecutorService串行处理所有写操作,避免并发写库导致SQLiteDatabaseLockedException。

public class CodeRepository { private final ExecutorService executor = Executors.newSingleThreadExecutor(); public void insert(VerifyCode code) { executor.execute(() -> { // 执行 insert }); } }

有的人可能觉得这不就是个数据库吗,有啥好说的。但这个工具的实际体验,恰恰就取决于数据库操作是否流畅。短信广播密集时,每秒可能进来好几条,如果全走主线程,界面直接卡死,连验证码都看不到了。

2.4 UI 展示:这张表要长成什么样

UI 我做得比较朴素,核心就是一个列表页。列表项展示三样东西:

  • 第一行:验证码 + 复制按钮。
  • 第二行:发送方号码。
  • 第三行:接收时间。

具体布局就是一个RecyclerView,每一条 item 都是 CardView 包裹的线性布局。复制按钮单独放在右侧,点击时把验证码写入剪贴板。

RecyclerView 的 adapter 我用了ListAdapter配合DiffUtil,这样数据更新之后能高效地增量刷新,避免整个列表闪烁重绘。数据源我用的是LiveData<List<VerifyCode>>,仓库里返回的查询结果直接扔给 UI 层观察,简单且不用自己管生命周期。

界面上还加了两个实用功能:

  • 搜索框:根据验证码、号码、短信原文做模糊搜索,用LIKE查询实现。联调的时候,想找某一次测试的验证码,输个号码前缀就出来了。
  • 清空按钮:一键清空历史记录。测试时经常刷出几十条垃圾记录,一键清空比一条条滑删方便太多。

颜色和动效我没做过多的设计,毕竟这是工具性页面,不是产品首页。唯一做的细节是:验证码文字字号调大,加粗,方便从远处瞄一眼就知道是什么。复制成功之后弹个 Toast,或者按钮上短暂显示个“已复制”状态,都行。

3. 实操过程与核心环节实现

3.1 环境准备与工程结构

我用的开发环境是 Android Studio,语言用的是 Java。如果你更习惯 Kotlin,实现逻辑完全一样,把BroadcastReceiver、RecyclerView换成 Kotlin 写法即可,核心思路没有区别。

工程结构大概是这样的:

app/src/main/java/com/example/codetable/ ├── receiver/ │ └── SmsReceiver.java ├── db/ │ ├── CodeDbHelper.java │ └── VerifyCode.java ├── repository/ │ └── CodeRepository.java ├── ui/ │ ├── MainActivity.java │ ├── CodeListAdapter.java │ └── activity_main.xml

这里要注意包名不要照抄我示例里的com.example,正式工具一定要换成自己的包名,否则打包签名的时候会各种报错。

3.2 权限声明与动态申请

Manifest 里要加这几条权限和 receiver 声明:

<uses-permission android:name="android.permission.RECEIVE_SMS" /> <uses-permission android:name="android.permission.READ_SMS" /> <application ...> <receiver android:name=".receiver.SmsReceiver" android:exported="true" android:permission="android.permission.BROADCAST_SMS"> <intent-filter android:priority="1000"> <action android:name="android.provider.Telephony.SMS_RECEIVED" /> </intent-filter> </receiver> </application>

细节说明一下:

  • android:exported="true"是必需的,因为系统要能把广播发进来。
  • android:permission="android.permission.BROADCAST_SMS"这行是保护这个 receiver 不被其他应用乱触发,加上更安全。
  • android:priority="1000"是提高接收优先级。如果手机里同时装了好几个短信拦截 App,优先级高的会先拿到广播。我实测下来优先级太重要了,某些安全软件会抢先消费掉这个广播,导致我的工具收不到。

动态申请权限的代码我也顺便贴下,标准写法:

if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_SMS) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECEIVE_SMS}, REQUEST_CODE_SMS); }

记得要在onRequestPermissionsResult里处理拒绝的情况,不能让 App 无声无息地废掉。用户拒绝后,最好弹个设置引导,不然很多人根本不知道去哪开权限。

3.3 短信广播接收与入库串联

整个数据流的串联,我在SmsReceiver里做的是:

public class SmsReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (!Telephony.Sms.Intents.SMS_RECEIVED_ACTION.equals(intent.getAction())) { return; } Serializable messages = intent.getSerializableExtra("pdus"); // 这里实际要用 Telephony.Sms.Intents.getMessagesFromIntent(intent) SmsMessage[] smsMessages = Telephony.Sms.Intents.getMessagesFromIntent(intent); for (SmsMessage sms : smsMessages) { if (sms == null) continue; String message = sms.getMessageBody(); String address = sms.getOriginatingAddress(); String code = CodeParser.parse(message); if (!TextUtils.isEmpty(code)) { // 排队写库 CodeRepository.getInstance().insert( new VerifyCode(code, address, message, System.currentTimeMillis())); } } } }

这里有一个我正在用的辅助工具类,封装了数据库的插入:

public class CodeRepository { private static CodeRepository instance; private final CodeDbHelper dbHelper; private final ExecutorService executor = Executors.newSingleThreadExecutor(); private CodeRepository(Context context) { dbHelper = new CodeDbHelper(context.getApplicationContext()); } public static synchronized CodeRepository getInstance(Context context) { if (instance == null) { instance = new CodeRepository(context); } return instance; } public void insert(VerifyCode code) { executor.execute(() -> { SQLiteDatabase db = dbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("code", code.getCode()); values.put("phone", code.getPhone()); values.put("message", code.getMessage()); values.put("received_time", code.getReceivedTime()); db.insert("verify_code", null, values); }); } public LiveData<List<VerifyCode>> getAllCodes() { // 用 MutableLiveData + 查询线程 } }

任何首次接触的人可能都会问:为什么用单线程 Executor?因为 SQLite 本身只允许一个写连接,而广播接收可能短时间内到达多个onReceive,如果同时开启多线程写库,很容易出现database is locked。单线程串行写入虽然牺牲了一丁点并发性能,但换来的是稳定性,这笔账怎么算都划算。

3.4 列表展示与复制交互

MainActivity 里搭界面属于常规操作,但有几个细节我反复打磨过。

RecyclerView 的 item 布局,我设计成横向两段式:

  • 左侧权重 1,显示验证码、号码、时间。
  • 右侧固定宽度,放一个带图标文字的“复制”按钮。

复制功能核心代码:

ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newPlainText("verify_code", item.getCode()); clipboard.setPrimaryClip(clip);

高版本 Android 上,复制之后系统会弹“已复制”的提示,部分机型不需要自己再弹 Toast 了。但我实测下来,小米和华为的定制系统提示不太一样,为了统一,我还是自己弹了个轻量 Toast,确保用户一定能看到反馈。

还有个小细节:列表时间显示要人性化。存的是毫秒时间戳,但直接显示成一串数字没人看得懂。我做了个转换:

private String formatTime(long timestamp) { SimpleDateFormat sdf = new SimpleDateFormat("MM-dd HH:mm:ss", Locale.getDefault()); return sdf.format(new Date(timestamp)); }

如果时间跨度超过一天,就显示日期;如果只是今天,可以加个“今天”前缀。这种细节对工具类应用的使用频率影响很大。看起来无所谓,实际上你每次都翻昨天的情况,没有格式化时间会觉得非常痛苦。

3.5 手动补录:一个看似多余但救命的功能

这个功能是后加的,但实测下来帮了大忙。

有些验证码短信是 App 内部自定义通道下发的,不经过系统短信,还有的验证码是语音播报的,永远进不了短信广播。针对这些漏网之鱼,我在主界面塞了一个“手动添加”的入口,弹个对话框,让你手填验证码和备注,然后插进表里。

实现其实就是一个普通的AlertDialog+ 编辑框 +insert(),但价值在于这表里的数据完整性。测试的时候,如果某个码是从别处拿到的,手动补录进去之后,整个测试流程的记录就齐全了。表格不再只是“短信验证码表”,而是一个“全渠道验证码管理表”。

4. 常见问题与排查技巧实录

4.1 收不到验证码广播怎么办

这是最常遇到的问题,也是第一批反馈里最多的。我把实测过的排查步骤列一下:

  • 确认权限已授权,不是只点过一次就以为成功了。很多定制系统会在授权后再次弹窗问“是否允许后台接收短信”,必须确认。
  • 确认系统没有把 App 加入电池优化白名单。在设置里搜索“电池优化”,把应用设置为“不优化”,否则系统深度休眠时广播根本发不进来。
  • 确认没有其他应用抢先拦截短信。尤其手机自带的安全管家,或者装有短信拦截工具,会吃掉广播,导致优先级低的接收器拿不到。我实测android:priority="1000"能解决大多数抢广播的问题,但不是 100%。
  • 重启手机后,有些定制系统的广播恢复需要一段时间,或者根本不恢复,这时候需要手动打开 App 一次,让进程活跃起来。

如果把这几项都排查完了还是收不到,大概率是定制系统限制了第三方应用的短信广播。终极保底方案是:在 App 内提供一个手动刷新按钮,动态读取系统短信数据库(需要READ_SMS权限),把最近五分钟内的验证码补录到表里。这样就算广播被掐了,手动刷新也能兜底。

4.2 验证码解析不准、漏码或错码

解析不准的原因我总结成三种:

  • 短信里有链接、有数字账户余额等信息,正则被长数字串干扰。处理方案:先剥离 URL 再解析。
  • 多段短信拼接时,getMessagesFromIntent返回的数组需要逐条拼接,而不是只取第一条。很多多段长短信就是把验证码放在最后一段,只取第一条自然永远解析不到。
  • 验证码是纯英文字母的组合,比如“Your code is aBcDeF”。纯数字正则根本不匹配,要加字母数字混合正则。

我还遇到过一种比较隐蔽的情况:某银行发的验证码格式是“您正在申请开通服务,验证码为 8888,如非本人操作,请忽略。退款验证码也请勿泄露”。这里有两个“码”字,我的正则如果优先定位第一个关键词,可能取到“为 8888”没问题,但如果短信里既有订单号又有验证码,就会选错。解决办法是多关键词匹配时,取最后一个出现的验证码关键词,因为通常验证码都在句尾强调。

4.3 列表卡顿、数据更新不及时

列表卡顿一般不是 RecyclerView 的问题,而是每次短信过来都调notifyDataSetChanged(),导致整个列表重绘。我用ListAdapter+DiffUtil后就再没遇到这个问题。

数据更新不及时的表现是:短信已经进来了,但页面没弹出新数据。这是因为我早期用的是在onReceive里直接更新 UI,但广播回调不一定在主线程,而且 Activity 可能没在前台。后来我把查询结果改成LiveData,在仓库里操作完数据库后,通过LiveData.postValue()通知 UI,这样无论 App 在前台后台、屏幕亮灭,数据一到都能正确刷新。

postValue和setValue还有个区别值得说:setValue只能在主线程调用,而postValue可以从后台线程调用。仓库里的线程池写库完成后,用postValue是标准做法。

4.4 数据库越来越大的清理策略

历史验证码看着是小事,但天长日久也会膨胀。我加了两条清理策略:

  • 默认只保留最近 30 天的数据,启动时或者插入数据时检查一次,删除超过 30 天的旧记录。
  • 表里加了个开关,打开“自动清理”后,每次插入新记录都会顺手删掉最老的 N 条,保证表里最多只留 500 条。

删除的 SQL 很简单:

DELETE FROM verify_code WHERE received_time < ?

这个一个定时逻辑就够,插一条删一批,数据量完全可控。对 UI 来说,列表最多 500 条,滑动也很流畅。

4.5 高版本 Android 的适配细节

现在新手机基本都是 Android 13、Android 14 了,有几个适配问题必须提前注意:

  • Runtime Permission弹窗在部分系统上只能弹一次,拒绝后不再自动弹出。第二次要引导用户去“应用设置”里开权限,我加了个跳转到Settings.ACTION_APPLICATION_DETAILS_SETTINGS的入口。
  • Android 13 起,部分设备对RECEIVE_SMS权限增加了“不常用应用”的自动回收机制。如果 App 长时间不打开,权限会被自动撤掉。工具类 App 天天都要用,问题不大,但如果偶尔才用一次,要注意重新授权。
  • Android 12 之后,系统会限制后台应用的某些操作,但短信广播本身不受影响,反而是一些厂商的省电策略影响最大。这也是我为什么要单独把“电池优化白名单”写进排查清单的原因。
  • 如果你的编译目标版本是 Android 14,SmsMessage.getOriginatingAddress()依旧可用,没有废弃,放心用。

5. 从“能用”到“好用”的几个增益改造

基础版做完之后,我用了一周左右,又陆续加了几个小功能,使用体验上了一个台阶。

第一个是验证码关联上下文。很多验证码是跟业务场景绑定的,光看到一串数字不知道是哪次操作触发的。我在短信原文解析时,把关键词上下文也存进表里,比如“您正在【某平台】进行登录操作,验证码为 123456”,那我提取验证码的同时,也把“某平台登录”提取出来放到备注字段。这样表格上第一眼看到的就不只是验证码,还有场景提示,效率加成非常明显。

第二个是验证码一键填入。这个功能在调试 App 内表单时极其好用。点击列表项,不仅复制验证码到剪贴板,还自动尝试填到当前输入框(如果当前 App 有悬浮窗权限的话)。但悬浮窗功能的权限申请又是一堆麻烦事,后来我保留的是“复制后自动打开目标测试 App”的配置,配合剪贴板读取,实现“复制即填入”的近似体验。

第三个是导出 CSV。联调结束后需要跟后端同学对账,或者把测试记录贴到缺陷单里,直接把 CSV 文件导出到本地,就不用手动截图拼表格了。导出的列正好是验证码、号码、时间、原文,跟缺陷单里要贴的信息完全对得上。

这三个功能单独看都不复杂,但合在一起,这个验证码表就从一个“查看器”变成了一个“工作台”。我后来的工作流变成了:手机开启悬浮窗小表,在电脑上发起一条测试短信,手机弹出新验证码,我拿起手机点一下,验证码就进剪贴板了,再回到电脑粘贴。整个过程不超过三秒,效率提升立竿见影。

最后再聊个细节。很多人做这类工具,做完了自己用几天就丢一边了。但我建议你把解析正则、权限适配这几块单独做成配置项,放到一个本地配置文件里,因为不同测试环境的短信格式真的千差万别。今天这个平台验证码是六位数字,明天那个平台就是四位的;今天关键词是“验证码”,明天英文场景是“don't share it with anyone”。把这些做成可配置项之后,团队里其他人拿去用,不用改代码、不用重新打包,改完配置重启就生效,运维成本趋近于零。

我做这个小工具最大的体会是:验证码看起来是件小事,但它是账号链路里最常被打断的环节。把这条链路单独抽出来做成一张清晰的表,不光是省了翻短信的时间,整个联调节奏都会顺畅很多。如果你的测试环境也经常被验证码困扰,不妨按这个思路搞一个,保证你再也不想回到满屏短信里扒验证码的日子。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进