如何通过WebJob每日将SharePoint Online库中图片保存至内网远程服务器?
实现方案
一、获取SharePoint Online文档库中的图片
要读取SharePoint Online的图片,推荐用Microsoft Graph API,步骤如下:
- 先在Azure AD注册应用,拿到客户端ID、客户端密钥、租户ID,给应用分配
Sites.Read.All权限(需要管理员同意) - 用Microsoft Graph SDK编写代码,遍历文档库里的图片文件,获取文件流(比Base64更高效)
示例代码片段:
using Microsoft.Graph; using Azure.Identity; var scopes = new[] { "https://graph.microsoft.com/.default" }; var tenantId = "你的租户ID"; var clientId = "你的客户端ID"; var clientSecret = "你的客户端密钥"; var siteId = "你的站点ID"; var driveId = "文档库ID"; var credential = new ClientSecretCredential(tenantId, clientId, clientSecret); var graphClient = new GraphServiceClient(credential, scopes); // 过滤出文档库中的图片文件 var imageItems = await graphClient.Sites[siteId].Drives[driveId].Root.Children .Request() .Filter("file ne null and file/mimeType starts with 'image/'") .GetAsync();
二、保存图片到内网非SFTP服务器
你的现有代码是存到SMB共享路径(\\remote server\share\temp\),如果目标服务器也是SMB共享,直接复用逻辑即可,优化下路径拼接和去重:
static string saveFolder = @"\\remote server\share\temp\"; public static void SaveImageFromStream(Stream imageStream, string fileName) { try { // 去掉原文件后缀,统一存为jpg var baseName = Path.GetFileNameWithoutExtension(fileName); var savePath = Path.Combine(saveFolder, $"{baseName}.jpg"); // 避免覆盖已存在文件,加时间戳 if (File.Exists(savePath)) { savePath = Path.Combine(saveFolder, $"{baseName}_{DateTime.Now:yyyyMMddHHmmss}.jpg"); } using (Image image = Image.FromStream(imageStream)) { image.Save(savePath); } } catch (Exception ex) { Console.WriteLine($"保存图片失败:{ex.ToString()}"); } }
注意:运行程序的账号必须有该SMB共享的读写权限,否则会报错。
三、实现每日定时运行
推荐用Windows任务计划程序,简单适配内网环境:
- 把代码编译成控制台.exe文件
- 打开Windows任务计划程序,新建基本任务:
- 设置触发条件为「每日」,指定运行时间
- 操作选择「启动程序」,选中你的.exe文件
- 配置运行账号,勾选「不管用户是否登录都要运行」,确保后台自动执行
如果有服务器上的.NET Web应用,也可以用Hangfire来配置定时任务;有Azure环境的话,也能创建定时触发的Azure Function(前提是Azure能打通内网)。
四、完整整合代码
把上述逻辑整合起来,完整的控制台程序示例:
using Microsoft.Graph; using Azure.Identity; using System.Drawing; using System.IO; class Program { static string saveFolder = @"\\remote server\share\temp\"; static async Task Main(string[] args) { var scopes = new[] { "https://graph.microsoft.com/.default" }; var tenantId = "你的租户ID"; var clientId = "你的客户端ID"; var clientSecret = "你的客户端密钥"; var siteId = "你的站点ID"; var driveId = "文档库ID"; try { var credential = new ClientSecretCredential(tenantId, clientId, clientSecret); var graphClient = new GraphServiceClient(credential, scopes); var imageItems = await graphClient.Sites[siteId].Drives[driveId].Root.Children .Request() .Filter("file ne null and file/mimeType starts with 'image/'") .GetAsync(); foreach (var item in imageItems) { var fileStream = await graphClient.Sites[siteId].Drives[driveId].Items[item.Id].Content.Request().GetAsync(); SaveImageFromStream(fileStream, item.Name); } Console.WriteLine("所有图片已保存完成"); } catch (Exception ex) { Console.WriteLine($"执行出错:{ex.ToString()}"); } } public static void SaveImageFromStream(Stream imageStream, string fileName) { try { var baseName = Path.GetFileNameWithoutExtension(fileName); var savePath = Path.Combine(saveFolder, $"{baseName}.jpg"); if (File.Exists(savePath)) { savePath = Path.Combine(saveFolder, $"{baseName}_{DateTime.Now:yyyyMMddHHmmss}.jpg"); } using (Image image = Image.FromStream(imageStream)) { image.Save(savePath); } } catch (Exception ex) { Console.WriteLine($"保存图片【{fileName}】失败:{ex.ToString()}"); } } }
关键注意事项
- 权限:运行程序的账号要同时拥有SharePoint文档库的读取权限、内网共享文件夹的写入权限
- 日志:可以把异常信息写入本地日志文件,方便后续排查问题
- 依赖:需要安装NuGet包:
Microsoft.Graph、Azure.Identity、System.Drawing.Common(注意.NET版本兼容性) - 去重优化:如果不想重复保存相同图片,可以对比文件的哈希值或修改时间,只同步新增/更新的图片
内容的提问来源于stack exchange,提问作者Kumbhar Kanchan
相关产品推荐
相关产品推荐

