基于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); } } }
关键说明
- 权限要求:确保你的
SCOPE包含Mail.Read权限,否则无法读取附件内容 - 附件类型处理:
FileAttachment:直接读取contentBytes属性即可获取文件内容ItemAttachment(如附件是邮件、日历项):需要调用/me/messages/{messageId}/attachments/{attachmentId}/$value接口获取内容,或通过Graph API展开其内部实体
- 大附件处理:如果附件超过4MB,需使用分块下载方式,上述代码适用于小附件场景
内容的提问来源于stack exchange,提问作者Nitin Rathod
相关产品推荐
相关产品推荐

