Java实现OTP接收与连续使用:替换Thread.sleep的等待方案咨询
替换Thread.sleep,实现可靠的Gmail OTP邮件等待机制
你的代码依赖固定休眠时间等待新邮件,但这种方式无法适配网络延迟或邮件推送速度差异,且默认取列表首封邮件的逻辑没有区分新旧,容易拿到过期OTP。以下是重构后的可靠方案:
核心改进点
- 记录初始最新邮件ID:等待前先获取当前邮箱的最新邮件ID,后续轮询时对比是否有新邮件到达
- 带超时的轮询机制:设置最大等待时长和轮询间隔,避免无限阻塞,同时比固定休眠更高效
- 过滤目标OTP邮件:通过发件人、主题等特征筛选邮件,确保拿到的是刚发送的目标OTP邮件
重构后的代码
public class GmailAPI { public static final String CREDENTIALS_FILE_PATH = "src/resources/credentials.json"; private static final String APPLICATION_NAME = "triodos_email"; private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); private static final String USER_ID = "me"; //current emailId private static final Logger log = LogManager.getLogger(GmailAPI.class); static Gmail service = null; public static String getOTPFromInbox() throws InterruptedException, IOException, GeneralSecurityException { // 1. 获取初始最新邮件ID String latestMessageId = getLatestMessageId(); long startTime = System.currentTimeMillis(); long maxWaitTime = 30000; // 最大等待30秒,可按需调整 long pollInterval = 1000; // 每秒轮询一次,可按需调整 // 2. 轮询等待新邮件 while (System.currentTimeMillis() - startTime < maxWaitTime) { ListMessagesResponse listOfMessages = getListOfMessages(CREDENTIALS_FILE_PATH); if (listOfMessages == null || listOfMessages.getMessages().isEmpty()) { Thread.sleep(pollInterval); continue; } String currentLatestId = listOfMessages.getMessages().get(0).getId(); // 检查是否有新邮件 if (!currentLatestId.equals(latestMessageId)) { Message newMessage = getParticularMessageFromListMessagesResponseBy(0, listOfMessages); if (newMessage != null && isOTPMessage(newMessage)) { // 验证是否是目标OTP邮件 return getOTPFromMessageSnippet(newMessage); } } Thread.sleep(pollInterval); } // 超时未获取到新邮件 log.error("超时未收到新的OTP邮件"); throw new IllegalStateException("Failed to receive OTP email within timeout"); } // 辅助方法:获取当前最新邮件ID private static String getLatestMessageId() throws IOException, GeneralSecurityException { initGmailService(); Gmail.Users.Messages.List list = service.users().messages().list(USER_ID).setMaxResults(1L); ListMessagesResponse response = list.execute(); if (response.getMessages() != null && !response.getMessages().isEmpty()) { return response.getMessages().get(0).getId(); } return ""; } // 辅助方法:判断是否是目标OTP邮件(根据实际情况调整发件人、主题) private static boolean isOTPMessage(Message message) throws IOException { List<MessagePartHeader> headers = message.getPayload().getHeaders(); String subject = ""; String from = ""; for (MessagePartHeader header : headers) { if ("Subject".equalsIgnoreCase(header.getName())) { subject = header.getValue(); } else if ("From".equalsIgnoreCase(header.getName())) { from = header.getValue(); } } // 替换为你的OTP邮件特征,比如发件人邮箱、主题关键词 return from.contains("otp-sender@your-domain.com") && subject.contains("登录验证码"); } // 初始化Gmail service,避免重复创建 private static void initGmailService() throws IOException, GeneralSecurityException { if (service == null) { GoogleCredentials credentials = getGoogleCredentials(); HttpRequestInitializer requestInitializer = new HttpCredentialsAdapter(credentials); final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); service = new Gmail.Builder(HTTP_TRANSPORT, JSON_FACTORY, requestInitializer) .setApplicationName(APPLICATION_NAME) .build(); } } public static GoogleCredentials getGoogleCredentials() throws IOException { try (InputStream credentialsStream = new FileInputStream(CREDENTIALS_FILE_PATH)) { GoogleCredentials credentials = GoogleCredentials.fromStream(credentialsStream) .createScoped(Arrays.asList("https://www.googleapis.com/auth/gmail.modify")); return credentials; } } public static ListMessagesResponse getListOfMessages(String credentialsFilePath) { log.info(String.format("Reading Gmail API credentials from: %s", credentialsFilePath)); try { initGmailService(); // 复用已创建的service log.info("Getting list of messages from Gmail inbox..."); Gmail.Users.Messages.List list = service.users().messages().list(USER_ID); ListMessagesResponse listMessagesResponse = list.execute(); if (listMessagesResponse.getMessages().size() == 0) { log.error("Empty list of messages. Check GmailAPI.getListOfMessages()"); throw new IllegalArgumentException(); } return listMessagesResponse; } catch (IOException | GeneralSecurityException e) { log.error(String.format("Exception was thrown in GmailAPI.getListOfMessages(): %s", e)); return null; } } public static Message getParticularMessageFromListMessagesResponseBy(Integer messageSequenceNumber, ListMessagesResponse listOfMessages) { String messageId = listOfMessages.getMessages().get(messageSequenceNumber).getId(); try { log.info(String.format("Getting %d message from the <listOfMessages>...", messageSequenceNumber)); Message message = service.users().messages().get(USER_ID, messageId).execute(); return message; } catch (IOException e) { log.error(String.format("IOException was thrown in GmailAPI.getParticularMessageFromListMessagesResponse(): %s", e)); return null; } } public static String getOTPFromMessageSnippet(Message message) { String regEx = ":"; log.info("Getting OTP from message snippet..."); log.info(String.format("Splitting email body snippet string by '%s'", regEx)); //check validity of OTP (if it contains letters or not enough digits) String body = StringUtils.newStringUtf8(Base64.decodeBase64(message.getPayload().getParts().get(0).getParts().get(0).getBody().getData())); Document doc = Jsoup.parse(body); Elements otpElements = doc.select(".email-otp strong"); String otp = otpElements.text(); if (otp.length() != 6 || !otp.matches("\\d+")) { log.error(String.format("Provided OTP invalid and contains either letters or short digits sequence: %s", otp)); log.error("Check GmailAPI.getOTPFromMessageSnippet() method "); throw new IllegalArgumentException(); } return otp; } }
关键说明
- 轮询逻辑:每次轮询对比最新邮件ID,发现新邮件后先验证是否为目标OTP邮件,再提取验证码
- 超时控制:通过
maxWaitTime限制最长等待时间,避免程序无限阻塞 - 邮件过滤:
isOTPMessage方法可根据实际业务调整筛选规则,避免误取其他无关邮件 - Service复用:新增
initGmailService方法避免重复创建Gmail服务实例,提升性能
内容的提问来源于stack exchange,提问作者Maksym Avanesov
相关产品推荐
相关产品推荐

