基于Flutter實(shí)現(xiàn)短信驗(yàn)證碼監(jiān)控與轉(zhuǎn)發(fā)
1. 前言
前段時間,我基于deepseek制作了一個基于小紅書的自動推文生成發(fā)送工作流。然而,先前制作的windows端的工作流到小紅書發(fā)布時顯得異常繁瑣,原先的思路是在手機(jī)接收到驗(yàn)證碼后進(jìn)入系統(tǒng)進(jìn)行人為輸入,這顯然太麻煩了。同時,這一問題當(dāng)部署到linux服務(wù)器上時顯得尤為突出,這與自動化的理念顯然有些背道而馳。因此,我決定基于flutter制作一個驗(yàn)證碼提取轉(zhuǎn)發(fā)應(yīng)用,將手機(jī)短信驗(yàn)證碼提取出來,通過http接口轉(zhuǎn)發(fā)給工作流,從而實(shí)現(xiàn)自動化的工作流。
2.開發(fā)環(huán)境
- IDE:VSCode
- 語言:Dart 3.7.0
- 框架:Flutter 3.29.0
3. 實(shí)現(xiàn)思路
3.1 驗(yàn)證碼提取
flutter中存在大量不錯的第三方短信處理庫,例如flutter_sms_inbox, sms_v2, sms_receiver等,但經(jīng)過測試,許多庫在當(dāng)前開發(fā)環(huán)境下存在許多問題,因此我最終選擇了sms_advanced庫進(jìn)行短信處理。
sms_advanced提供了querySms()這樣一個方法,這個方法可以根據(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()方法可以接受多個參數(shù),其中address參數(shù)可以指定短信發(fā)送者的手機(jī)號,kinds參數(shù)默認(rèn)為SmsQueryKind.Inbox,即從收件箱獲取短信,從而實(shí)現(xiàn)短信提取。然后使用正則表達(dá)式對短信內(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)小紅書的驗(yàn)證碼發(fā)送者手機(jī)號并非固定,因此我選擇制作一個多條件篩選器。在條件篩選中,我選擇先根據(jù)手機(jī)號做一次短信篩選,如果沒有找到,則根據(jù)短信內(nèi)容做一次篩選,如果還是沒有找到,則返回未找到短信。這樣用戶就可以在僅知道驗(yàn)證碼發(fā)送應(yīng)用名稱的情況下,不填寫發(fā)送者手機(jī)號,獲取到短信并提取到驗(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)證碼通過http接口轉(zhuǎn)發(fā)給工作流。這里我選擇使用http庫進(jìn)行http請求,實(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)證碼提取是一個耗時操作,因此我選擇將其放在一個子線程中執(zhí)行,以避免阻塞主線程。這里我選擇使用flutter的Isolate進(jìn)行線程通信。同時,為了更新監(jiān)控狀態(tài)并控制監(jiān)控開始和停止,設(shè)計(jì)了兩個Port,分別是mainpPort和isolatePort。mainpPort用于向接收子線程的監(jiān)控狀態(tài)消息,實(shí)現(xiàn)監(jiān)控狀態(tài)的實(shí)時更新;isolatePort用于接收主線程發(fā)來的啟停信息,當(dāng)點(diǎn)擊停止監(jiān)控后,由父線程告知子線程停止作業(yè)。實(shí)現(xiàn)代碼如下:
MainPort:
// 在主isolate的接收端口監(jiān)聽中添加狀態(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 = '開始監(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ā)送停止信號,不強(qiáng)制終止Isolate
void _stopIsolate() {
if (_isolate != null) {
_isolateSendPort?.send('stop');
}
}
4. 總結(jié)
總體來說,整體項(xiàng)目還是挺簡單的。主要就是利用flutter的插件進(jìn)行短信的監(jiān)聽,然后通過正則表達(dá)式提取驗(yàn)證碼,最后通過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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Flutter中數(shù)據(jù)存儲的四種方式小結(jié)
在 Flutter 中,存儲是指用于本地和遠(yuǎn)程存儲和管理數(shù)據(jù)的機(jī)制,本給大家介紹了Flutter中不同存儲選項(xiàng)的概述和示例,通過代碼示例講解的非常詳細(xì),具有一定的參考價值,需要的朋友可以參考下2023-11-11
Android 自定義加載動畫Dialog彈窗效果的示例代碼
這篇文章主要介紹了Android 自定義加載動畫Dialog彈窗效果,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06
Android窗口小部件基礎(chǔ)編寫代碼實(shí)例
這篇文章主要介紹了Android窗口小部件基礎(chǔ)編寫代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-11-11
Android自定義控件實(shí)現(xiàn)底部菜單(下)
這篇文章主要為大家詳細(xì)介紹了Android自定義控件實(shí)現(xiàn)底部菜單的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-01-01
Android程序開發(fā)之使用PullToRefresh實(shí)現(xiàn)下拉刷新和上拉加載
這篇文章主要介紹了Android程序開發(fā)之使用PullToRefresh實(shí)現(xiàn)下拉刷新和上拉加載的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-07-07
Android?中?FrameLayout?布局及屬性的使用詳解
這篇文章主要介紹了Android?中?FrameLayout?布局及屬性的使用,FrameLayout?在實(shí)現(xiàn)簡單布局時非常方便,特別適用于疊加式布局,如顯示疊加的圖層或浮動按鈕等,需要的朋友可以參考下2024-03-03

