You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 14:42:05