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

基于Java与Microsoft Graph API实现邮件附件下载

问题描述

我需要通过MSAL和Microsoft Graph API读取邮件,读取完成后检查是否存在附件;若存在附件,则下载附件的content/contentbytes并保存到本地系统。目前我已实现读取邮件详情及附件元数据的代码,但不清楚如何编写下载附件的逻辑:

package com.xxx.xxxxx.msaltest.client;

import static com.xxx.xxxxxx.msaltest.constants.Constants.AUTHORITY_COMMON_ENDPOINTS;
import static com.xxx.xxxxxx.msaltest.constants.Constants.CLIENT_ID;
import static com.xxx.xxxxxx.msaltest.constants.Constants.SCOPE;

import java.util.List;
import java.util.function.Consumer;

import org.apache.commons.lang.StringUtils;

import com.microsoft.aad.msal4j.DeviceCode;
import com.microsoft.aad.msal4j.DeviceCodeFlowParameters;
import com.microsoft.aad.msal4j.IAuthenticationResult;
import com.microsoft.aad.msal4j.PublicClientApplication;
import com.microsoft.graph.authentication.IAuthenticationProvider;
import com.microsoft.graph.models.Attachment;
import com.microsoft.graph.models.Message;
import com.microsoft.graph.models.User;
import com.microsoft.graph.requests.AttachmentCollectionPage;
import com.microsoft.graph.requests.GraphServiceClient;
import com.microsoft.graph.requests.MessageCollectionPage;

import okhttp3.Request;

public class DeviceCodeClientMsal2 implements Runnable {

    public static String COMMON_VARIABLE = StringUtils.EMPTY;

    public DeviceCodeClientMsal2() {

    }

    @Override
    public void run() {
        IAuthenticationResult result;
        try {
            result = acquireTokenDeviceCode();
            System.out.println("** Access token: " + result.accessToken());
            System.out.println("** Id token: " + result.idToken());
            System.out.println("** Account username: " + result.account().username());

            IAuthenticationProvider provider = new MyAuthenticationProvider(result.accessToken());

            GraphServiceClient<Request> graphServiceClient = GraphServiceClient.builder()
                    .authenticationProvider(provider).buildClient();

            User user = graphServiceClient.me().buildRequest().get();

            final MessageCollectionPage messagePage = graphServiceClient.me().messages().buildRequest().top(3).get();

            List<Message> messageList = messagePage.getCurrentPage();
            for (Message msg : messageList) {
                System.out.println("Subject -> " + msg.subject);
                System.out.println("Body -> " + msg.body.toString());
                System.out.println("Is attachment exist ? " + msg.hasAttachments);
                if (msg.hasAttachments) {
                    System.out.println("Message id -> " + msg.id);
                    AttachmentCollectionPage attachments = graphServiceClient.me().messages(msg.id).attachments()
                            .buildRequest().get();
                    List<Attachment> listOfAttachment = attachments.getCurrentPage();
                    System.out.println("Size -> " + listOfAttachment.size());
                    Attachment attachment = listOfAttachment.get(0);
                    System.out.println("Attachment id -> " + attachment.id);
                    //How to download the attachment

                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("run method exits");
    }

    private static IAuthenticationResult acquireTokenDeviceCode() throws Exception {

        System.out.println("Before Public Client Application");
        PublicClientApplication pca = PublicClientApplication.builder(CLIENT_ID).authority(AUTHORITY_COMMON_ENDPOINTS)
                .build();

        IAuthenticationResult result;
        Consumer<DeviceCode> deviceCodeConsumer = (DeviceCode deviceCode) -> {
            COMMON_VARIABLE = deviceCode.message();
            System.out.println(deviceCode.message());
        };
        DeviceCodeFlowParameters parameters = DeviceCodeFlowParameters.builder(SCOPE, deviceCodeConsumer).build();
        result = pca.acquireToken(parameters).join();
        return result;
    }
}
解决方案

要实现附件下载,需注意只有FileAttachment类型的附件才包含可直接读取的contentBytes属性(其他类型如ItemAttachment需特殊处理)。以下是修改后的完整代码,添加了附件下载与本地保存逻辑:

package com.xxx.xxxxx.msaltest.client;

import static com.xxx.xxxxxx.msaltest.constants.Constants.AUTHORITY_COMMON_ENDPOINTS;
import static com.xxx.xxxxxx.msaltest.constants.Constants.CLIENT_ID;
import static com.xxx.xxxxxx.msaltest.constants.Constants.SCOPE;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;

import org.apache.commons.lang.StringUtils;

import com.microsoft.aad.msal4j.DeviceCode;
import com.microsoft.aad.msal4j.DeviceCodeFlowParameters;
import com.microsoft.aad.msal4j.IAuthenticationResult;
import com.microsoft.aad.msal4j.PublicClientApplication;
import com.microsoft.graph.authentication.IAuthenticationProvider;
import com.microsoft.graph.models.Attachment;
import com.microsoft.graph.models.FileAttachment;
import com.microsoft.graph.models.Message;
import com.microsoft.graph.models.User;
import com.microsoft.graph.requests.AttachmentCollectionPage;
import com.microsoft.graph.requests.GraphServiceClient;
import com.microsoft.graph.requests.MessageCollectionPage;

import okhttp3.Request;

public class DeviceCodeClientMsal2 implements Runnable {

    public static String COMMON_VARIABLE = StringUtils.EMPTY;

    public DeviceCodeClientMsal2() {

    }

    @Override
    public void run() {
        IAuthenticationResult result;
        try {
            result = acquireTokenDeviceCode();
            System.out.println("** Access token: " + result.accessToken());
            System.out.println("** Id token: " + result.idToken());
            System.out.println("** Account username: " + result.account().username());

            IAuthenticationProvider provider = new MyAuthenticationProvider(result.accessToken());

            GraphServiceClient<Request> graphServiceClient = GraphServiceClient.builder()
                    .authenticationProvider(provider).buildClient();

            User user = graphServiceClient.me().buildRequest().get();

            final MessageCollectionPage messagePage = graphServiceClient.me().messages().buildRequest().top(3).get();

            List<Message> messageList = messagePage.getCurrentPage();
            for (Message msg : messageList) {
                System.out.println("Subject -> " + msg.subject);
                System.out.println("Body -> " + msg.body.toString());
                System.out.println("Is attachment exist ? " + msg.hasAttachments);
                if (msg.hasAttachments) {
                    System.out.println("Message id -> " + msg.id);
                    AttachmentCollectionPage attachments = graphServiceClient.me().messages(msg.id).attachments()
                            .buildRequest().get();
                    List<Attachment> listOfAttachment = attachments.getCurrentPage();
                    System.out.println("Size -> " + listOfAttachment.size());
                    
                    // 遍历所有附件并下载
                    for (Attachment attachment : listOfAttachment) {
                        System.out.println("Attachment id -> " + attachment.id);
                        System.out.println("Attachment name -> " + attachment.name);
                        
                        // 仅处理文件类型附件
                        if (attachment instanceof FileAttachment) {
                            FileAttachment fileAttachment = (FileAttachment) attachment;
                            byte[] contentBytes = fileAttachment.contentBytes;
                            // 保存到本地
                            saveAttachmentToLocal(contentBytes, attachment.name);
                            System.out.println("附件 " + attachment.name + " 已成功保存");
                        } else {
                            System.out.println("附件 " + attachment.name + " 非文件类型,无法直接下载");
                        }
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("run method exits");
    }

    private static IAuthenticationResult acquireTokenDeviceCode() throws Exception {

        System.out.println("Before Public Client Application");
        PublicClientApplication pca = PublicClientApplication.builder(CLIENT_ID).authority(AUTHORITY_COMMON_ENDPOINTS)
                .build();

        IAuthenticationResult result;
        Consumer<DeviceCode> deviceCodeConsumer = (DeviceCode deviceCode) -> {
            COMMON_VARIABLE = deviceCode.message();
            System.out.println(deviceCode.message());
        };
        DeviceCodeFlowParameters parameters = DeviceCodeFlowParameters.builder(SCOPE, deviceCodeConsumer).build();
        result = pca.acquireToken(parameters).join();
        return result;
    }
    
    /**
     * 将附件字节内容保存到本地系统
     * @param contentBytes 附件字节数组
     * @param fileName 附件文件名
     * @throws IOException 文件操作异常
     */
    private void saveAttachmentToLocal(byte[] contentBytes, String fileName) throws IOException {
        // 自定义保存目录,当前项目下的attachments文件夹
        String saveDir = "attachments";
        File dir = new File(saveDir);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        File targetFile = new File(dir, fileName);
        try (FileOutputStream fos = new FileOutputStream(targetFile)) {
            fos.write(contentBytes);
        }
    }
}

关键说明

  1. 权限要求:确保你的SCOPE包含Mail.Read权限,否则无法读取附件内容
  2. 附件类型处理:
    • FileAttachment:直接读取contentBytes属性即可获取文件内容
    • ItemAttachment(如附件是邮件、日历项):需要调用/me/messages/{messageId}/attachments/{attachmentId}/$value接口获取内容,或通过Graph API展开其内部实体
  3. 大附件处理:如果附件超过4MB,需使用分块下载方式,上述代码适用于小附件场景

内容的提问来源于stack exchange,提问作者Nitin Rathod

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 15:42:33