基于Flutter實(shí)現(xiàn)短信驗(yàn)證碼監(jiān)控與轉(zhuǎn)發(fā)
1. 前言
前段時(shí)間,我基于deepseek制作了一個(gè)基于小紅書(shū)的自動(dòng)推文生成發(fā)送工作流。然而,先前制作的windows端的工作流到小紅書(shū)發(fā)布時(shí)顯得異常繁瑣,原先的思路是在手機(jī)接收到驗(yàn)證碼后進(jìn)入系統(tǒng)進(jìn)行人為輸入,這顯然太麻煩了。同時(shí),這一問(wèn)題當(dāng)部署到linux服務(wù)器上時(shí)顯得尤為突出,這與自動(dòng)化的理念顯然有些背道而馳。因此,我決定基于flutter制作一個(gè)驗(yàn)證碼提取轉(zhuǎn)發(fā)應(yīng)用,將手機(jī)短信驗(yàn)證碼提取出來(lái),通過(guò)http接口轉(zhuǎn)發(fā)給工作流,從而實(shí)現(xiàn)自動(dòng)化的工作流。
2.開(kāi)發(fā)環(huán)境
- IDE:VSCode
- 語(yǔ)言:Dart 3.7.0
- 框架:Flutter 3.29.0
3. 實(shí)現(xiàn)思路
3.1 驗(yàn)證碼提取
flutter中存在大量不錯(cuò)的第三方短信處理庫(kù),例如flutter_sms_inbox, sms_v2, sms_receiver等,但經(jīng)過(guò)測(cè)試,許多庫(kù)在當(dāng)前開(kāi)發(fā)環(huán)境下存在許多問(wèn)題,因此我最終選擇了sms_advanced庫(kù)進(jìn)行短信處理。
sms_advanced提供了querySms()這樣一個(gè)方法,這個(gè)方法可以根據(jù)條件進(jìn)行短信查詢。以下是方法源碼:
/// Query a list of SMS Future<List<SmsMessage>> querySms({ int? start, int? count, String? address, int? threadId, List<SmsQueryKind> kinds = const [SmsQueryKind.Inbox], bool sort = true}) async { List<SmsMessage> result = []; for (var kind in kinds) { result.addAll(await _querySmsWrapper( start: start, count: count, address: address, threadId: threadId, kind: kind, )); } if (sort == true) { result.sort((a, b) => a.compareTo(b)); } return (result); }
可以看到,querySms()方法可以接受多個(gè)參數(shù),其中address參數(shù)可以指定短信發(fā)送者的手機(jī)號(hào),kinds參數(shù)默認(rèn)為SmsQueryKind.Inbox,即從收件箱獲取短信,從而實(shí)現(xiàn)短信提取。然后使用正則表達(dá)式對(duì)短信內(nèi)容進(jìn)行匹配,提取出驗(yàn)證碼。
if (messages.isNotEmpty) { // 獲取第一條短信 SmsMessage firstMessage = messages.first; String? messageBody = firstMessage.body; // 使用正則表達(dá)式匹配驗(yàn)證碼,假設(shè)驗(yàn)證碼是 6 位數(shù)字 RegExp regex = RegExp(r'\d{6}'); Match? match = regex.firstMatch(messageBody!); if (match != null) { String smsCode = match.group(0)!; // 發(fā)送驗(yàn)證碼到 API result = await _sendCodeToAPI(smsCode); } else { result = '未在短信中找到驗(yàn)證碼'; } } else { result = '未找到短信'; }
但后面我發(fā)現(xiàn)小紅書(shū)的驗(yàn)證碼發(fā)送者手機(jī)號(hào)并非固定,因此我選擇制作一個(gè)多條件篩選器。在條件篩選中,我選擇先根據(jù)手機(jī)號(hào)做一次短信篩選,如果沒(méi)有找到,則根據(jù)短信內(nèi)容做一次篩選,如果還是沒(méi)有找到,則返回未找到短信。這樣用戶就可以在僅知道驗(yàn)證碼發(fā)送應(yīng)用名稱的情況下,不填寫(xiě)發(fā)送者手機(jī)號(hào),獲取到短信并提取到驗(yàn)證碼。實(shí)現(xiàn)代碼如下:
SmsQuery query = SmsQuery(); List<SmsMessage>? messages = await query.querySms( address: _phoneNumber, kinds: [SmsQueryKind.Inbox], ); // 獲取收件箱中的短信 if (messages.isEmpty) { List<SmsMessage>? messages = await query.querySms( kinds: [SmsQueryKind.Inbox], ); // 根據(jù)條件二進(jìn)行查詢 for (SmsMessage message in messages) { if (message.body?.contains(_targetApp) ?? false) { final code = _extractCode(message.body); if (code != null) { return await _sendCodeToAPI(code); } } } }
3.2 驗(yàn)證碼轉(zhuǎn)發(fā)
驗(yàn)證碼轉(zhuǎn)發(fā)是將提取到的驗(yàn)證碼通過(guò)http接口轉(zhuǎn)發(fā)給工作流。這里我選擇使用http庫(kù)進(jìn)行http請(qǐng)求,實(shí)現(xiàn)代碼如下:
Future<String?> _sendCodeToAPI(String code) async { try { final response = await http.post( Uri.parse(_apiEndpoint), headers: {'Content-Type': 'application/json'}, body: jsonEncode({'code': code}), ); if (response.statusCode != 200) { return ('Failed to send code: ${response.statusCode}'); } else { return 'Successfully Code sent '; } } catch (e) { return ('Error sending code: $e'); } }
3.3 線程通信
由于驗(yàn)證碼提取是一個(gè)耗時(shí)操作,因此我選擇將其放在一個(gè)子線程中執(zhí)行,以避免阻塞主線程。這里我選擇使用flutter的Isolate進(jìn)行線程通信。同時(shí),為了更新監(jiān)控狀態(tài)并控制監(jiān)控開(kāi)始和停止,設(shè)計(jì)了兩個(gè)Port,分別是mainpPort和isolatePort。mainpPort用于向接收子線程的監(jiān)控狀態(tài)消息,實(shí)現(xiàn)監(jiān)控狀態(tài)的實(shí)時(shí)更新;isolatePort用于接收主線程發(fā)來(lái)的啟停信息,當(dāng)點(diǎn)擊停止監(jiān)控后,由父線程告知子線程停止作業(yè)。實(shí)現(xiàn)代碼如下:
MainPort:
// 在主isolate的接收端口監(jiān)聽(tīng)中添加狀態(tài)更新 void _initReceivePort() { _receivePort = ReceivePort(); _receivePort.listen((message) { if (message is String) { if (message == 'isolate_stopped') { // 處理isolate退出通知 if (mounted) { setState(() { _isolate = null; isMonitoring = false; buttonText = '開(kāi)始監(jiān)控'; }); } } else { setState(() => response = message); if(message == 'Successfully Code sent') { _stopIsolate(); } } } else if (message is SendPort) { _isolateSendPort = message; } }); }
IsolatePort:
static void _monitorSmsInBackground(List<dynamic> args) async { final rootIsolateToken = args[4] as RootIsolateToken; BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken); final apiEndpoint = args[0] as String; final targetApp = args[1] as String; final phoneNumber = args[2] as String; final mainSendPort = args[3] as SendPort; final smsHandler = SMSHandler(apiEndpoint, targetApp, phoneNumber); final controlPort = ReceivePort(); mainSendPort.send(controlPort.sendPort); final stopCompleter = Completer<void>(); controlPort.listen((message) { if (message == 'stop') { stopCompleter.complete(); } }); try { while (!stopCompleter.isCompleted) { final value = await smsHandler.initSMSListener().timeout( const Duration(seconds: 1), onTimeout: () => null, ); print(value); mainSendPort.send(value); if (stopCompleter.isCompleted) break; } } finally { controlPort.close(); mainSendPort.send('isolate_stopped'); // 添加退出通知 } }
StopIsolate:
// 修改 _stopIsolate 方法,僅發(fā)送停止信號(hào),不強(qiáng)制終止Isolate void _stopIsolate() { if (_isolate != null) { _isolateSendPort?.send('stop'); } }
4. 總結(jié)
總體來(lái)說(shuō),整體項(xiàng)目還是挺簡(jiǎn)單的。主要就是利用flutter的插件進(jìn)行短信的監(jiān)聽(tīng),然后通過(guò)正則表達(dá)式提取驗(yàn)證碼,最后通過(guò)http接口將驗(yàn)證碼發(fā)送給工作流。但因?yàn)槌醮螌W(xué)習(xí)flutter,許多地方?jīng)]有做詳細(xì)的優(yōu)化,僅僅實(shí)現(xiàn)了整體功能。工程代碼放在github上,有興趣的可以看看:verify_code_app
到此這篇關(guān)于基于Flutter實(shí)現(xiàn)短信驗(yàn)證碼監(jiān)控與轉(zhuǎn)發(fā)的文章就介紹到這了,更多相關(guān)Flutter短信驗(yàn)證碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android自定義SeekBar實(shí)現(xiàn)滑動(dòng)驗(yàn)證且不可點(diǎn)擊
這篇文章主要為大家詳細(xì)介紹了Android自定義SeekBar實(shí)現(xiàn)滑動(dòng)驗(yàn)證且不可點(diǎn)擊,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03詳解Android使用Socket對(duì)大文件進(jìn)行加密傳輸
這篇文章主要介紹了詳解Android使用Socket對(duì)大文件進(jìn)行加密傳輸,使用Socket進(jìn)行文件傳輸過(guò)程時(shí),需要先進(jìn)行加密,有興趣的可以了解一下。2017-01-01關(guān)于Android的 DiskLruCache磁盤(pán)緩存機(jī)制原理
DiskLruCache是一種管理數(shù)據(jù)存儲(chǔ)的技術(shù),單從Cache的字面意思也可以理解到,"Cache","高速緩存";今天我們來(lái)從源碼上分析下DiskLruCache;關(guān)于Android LruCache的緩存機(jī)制原理,需要的朋友可以參考下面文章的具體內(nèi)容2021-09-09ListView實(shí)現(xiàn)下拉動(dòng)態(tài)渲染數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了ListView實(shí)現(xiàn)下拉動(dòng)態(tài)渲染數(shù)據(jù)的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06Android布局之絕對(duì)布局AbsoluteLayout詳解
這篇文章主要為大家詳細(xì)介紹了Android布局之絕對(duì)布局AbsoluteLayout的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10Android實(shí)現(xiàn)界面的自動(dòng)跳轉(zhuǎn)功能
界面自動(dòng)跳轉(zhuǎn)是指在應(yīng)用啟動(dòng)或某個(gè)特定界面顯示后,經(jīng)過(guò)預(yù)定的時(shí)間或者滿足某些條件后,自動(dòng)跳轉(zhuǎn)到另一個(gè)目標(biāo)界面,本文小編給大家講解了Android實(shí)現(xiàn)界面的自動(dòng)跳轉(zhuǎn)功能,感興趣的小伙伴跟著小編一起來(lái)看看吧2025-04-04