在开发应用时,我们经常需要实现数据同步功能。例如我之前开发的表单填写工具(QuickFillForm),如果不保存到网盘,换个浏览器数据就丢失了;但如果接入 OneDrive 同步,用户就能在各个设备上无缝使用。本文将以这个实际案例为例,详细说明如何申请和配置 OneDrive API,帮助开发者快速实现网盘同步功能。

应用场景

OneDrive API 适用于以下开发场景:

  • 跨设备数据同步: 浏览器插件、桌面应用的配置和数据同步
  • 文件备份与恢复: 自动备份用户数据到云端,支持一键恢复
  • 协作与分享: 实现文件共享、多人协作编辑功能
  • 离线访问: 支持离线读取数据,在线自动同步

前置准备

在开始之前,你需要准备:

  • ✅ Microsoft 账号(个人账号或工作/学校账号均可)
  • ✅ 开发环境(本文以浏览器插件为例)
  • ✅ 基础的 JavaScript/API 调用知识

💡 提示: OneDrive API 提供了免费的开发者配额,个人应用完全够用。


第一步: 注册 Azure 应用

1.1 访问 Azure Portal

  1. 打开浏览器,访问 Azure Portal
  2. 使用你的 Microsoft 账号登录
    • 个人账号(如 Outlook、Hotmail 邮箱)
    • 工作或学校账号(如公司/学校提供的 Office 365 账号)
  3. 如果没有 Azure 账号,可以免费注册

📌 注意: 即使没有 Azure 订阅,也可以注册应用。免费账号足够用于开发测试。

1.2 进入应用注册页面

  1. 在 Azure Portal 首页的搜索栏中输入 “Azure Active Directory”“Microsoft Entra ID”
  2. 点击搜索结果中的 Azure Active Directory 服务
  3. 在左侧菜单中找到 “应用注册” (App registrations)
  4. 点击顶部的 "+ 新注册" (New registration) 按钮

Responsive Image

Azure AD 应用注册入口

1.3 填写应用基本信息

在应用注册页面,需要填写以下关键信息:

应用名称

  • 字段: 名称 (Name)
  • 示例: QuickFillForm Extension我的数据同步应用
  • 说明: 这是应用的显示名称,用户授权时会看到此名称

支持的账户类型

选择应用的用户范围,推荐选择:

  • 任何组织目录(任何 Azure AD 目录 - 多租户)中的帐户和个人 Microsoft 帐户(例如,Skype、Xbox)
  • 英文选项: Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)

为什么选择这个选项?

  • 支持个人 Microsoft 账号(最广泛的用户群)
  • 支持企业和学校账号
  • 不限制组织,任何人都可以使用你的应用

其他选项说明:

  • 仅此组织目录: 仅限你的组织内部使用
  • 任何组织目录: 仅限企业/学校账号,不支持个人账号
  • 仅个人 Microsoft 账户: 仅支持个人账号

重定向 URI (Redirect URI)

这是 OAuth 认证完成后的回调地址,不同应用类型配置不同:

浏览器插件 (Chrome/Edge):

  • 平台类型: Web
  • URI 格式: https://<YOUR_EXTENSION_ID>.chromiumapp.org/onedrive
  • 临时示例: https://abcdefghijklmnopqrstuvwxyz123456.chromiumapp.org/onedrive

Web 应用:

  • 平台类型: Web
  • URI 格式: https://yourdomain.com/callback
  • 本地开发: http://localhost:3000/callback

桌面应用:

  • 平台类型: 公共客户端/本机
  • URI 格式: https://login.microsoftonline.com/common/oauth2/nativeclient

⚠️ 重要: 浏览器插件的 Extension ID 需要在插件安装后才能获取,稍后我们会更新这个配置。可以先填写一个临时值,或者留空后面再添加。

1.4 完成注册

确认信息无误后,点击页面底部的 “注册” (Register) 按钮。

注册成功后,会自动跳转到应用的 概述 (Overview) 页面,这里显示了应用的基本信息和重要 ID。


第二步: 配置 API 权限

注册完应用后,需要为应用添加访问 OneDrive 的权限。

2.1 进入 API 权限页面

  1. 在应用详情页面的左侧菜单中,点击 “API 权限” (API permissions)
  2. 你会看到默认已经添加了 User.Read 权限(这是用于读取用户基本信息的权限)

2.2 添加 Microsoft Graph 权限

  1. 点击页面中的 "+ 添加权限" (Add a permission) 按钮
  2. 在右侧弹出的面板中,点击 “Microsoft Graph”
  3. 选择 “委托的权限” (Delegated permissions)

💡 权限类型说明:

  • 委托的权限 (Delegated permissions): 应用代表已登录用户访问数据,需要用户授权
  • 应用程序权限 (Application permissions): 应用以自己的身份访问数据,无需用户登录,需要管理员同意

2.3 选择所需权限

在搜索框中搜索并勾选以下权限:

Files.ReadWrite.AppFolder (推荐)

  • 权限名称: Files.ReadWrite.AppFolder
  • 权限级别: 委托的权限
  • 作用: 允许应用读写应用专用文件夹中的文件
  • 优势: 最安全的权限,应用只能访问自己创建的 Apps/<AppName> 文件夹
  • 适用场景: 大多数应用数据同步场景

示例存储路径:

OneDrive/
└── Apps/
    └── QuickFillForm/
        ├── quickfillform-data.json
        └── backup/

offline_access (必需)

  • 权限名称: offline_access
  • 权限级别: 委托的权限
  • 作用: 允许应用在用户离线时访问数据
  • 用途: 获取刷新令牌(refresh token),在访问令牌过期后自动续期

Files.ReadWrite.All (可选,不推荐)

  • 权限名称: Files.ReadWrite.All
  • 权限级别: 委托的权限
  • 作用: 允许应用读写用户的所有文件
  • 风险: 权限范围过大,可能引起用户担忧
  • 适用场景: 需要访问用户整个 OneDrive 的应用(如文件管理器)

2.4 确认添加权限

  1. 勾选好权限后,点击底部的 “添加权限” (Add permissions) 按钮
  2. 返回 API 权限页面,确认列表中显示以下权限:
    • User.Read (默认)
    • Files.ReadWrite.AppFolder
    • offline_access

2.5 授予管理员同意 (可选)

  • 如果你是组织管理员,可以点击 “为 [组织名称] 授予管理员同意” 按钮
  • 对于个人账号或公共应用,此步骤可以跳过
  • 用户首次使用时会看到授权页面,点击"接受"即可

第三步: 获取应用凭据

3.1 复制 Client ID (应用程序 ID)

  1. 在应用详情页面的 “概述” (Overview) 选项卡中
  2. 找到 “应用程序(客户端) ID” (Application (client) ID)
  3. 这是一个 GUID 格式的字符串,类似:
    12345678-1234-1234-1234-123456789abc
    
  4. 点击旁边的复制图标,将 Client ID 保存到安全的地方

🔒 安全提示: Client ID 是公开信息,可以在前端代码中使用。但不要将它与 Client Secret 混淆。

3.2 是否需要 Client Secret?

对于不同类型的应用,Client Secret 的需求不同:

应用类型是否需要 Client Secret原因
浏览器插件❌ 不需要使用隐式授权流程或 PKCE
单页应用 (SPA)❌ 不需要前端代码无法安全存储密钥
Web 应用 (有后端)✅ 需要在服务器端安全存储
桌面/移动应用❌ 不需要使用 PKCE 授权流程

如果需要 Client Secret,创建方法如下:

  1. 在左侧菜单中点击 “证书和密码” (Certificates & secrets)
  2. 点击 "+ 新客户端密码" (New client secret)
  3. 输入描述(如 “生产环境密钥”),选择过期时间
  4. 点击 “添加” (Add)
  5. 立即复制密钥值,此密钥只显示一次

⚠️ 警告: Client Secret 是敏感信息,必须妥善保管,不能提交到代码仓库。

3.3 配置身份验证设置

  1. 在左侧菜单点击 “身份验证” (Authentication)
  2. 确认 重定向 URI 已正确添加
  3. 向下滚动到 “隐式授权和混合流” (Implicit grant and hybrid flows) 部分

对于浏览器插件或 SPA,需要启用:

  • ✅ 勾选 “访问令牌” (Access tokens)
  • ✅ 勾选 “ID 令牌” (ID tokens)

对于使用授权码流程的应用,可以不勾选上述选项

  1. 点击页面底部的 “保存” (Save) 按钮

第四步: 配置应用代码

以浏览器插件为例,说明如何在代码中配置 OneDrive API。

4.1 获取浏览器插件的 Extension ID

如果你正在开发浏览器插件:

  1. 打开 Chrome 或 Edge 浏览器
  2. 访问 chrome://extensions/ (Chrome) 或 edge://extensions/ (Edge)
  3. 开启右上角的 “开发者模式” (Developer mode)
  4. 点击 “加载已解压的扩展程序” (Load unpacked)
  5. 选择你的插件项目文件夹
  6. 安装成功后,在插件卡片上找到 “ID” 字段,复制扩展 ID
    • 类似: abcdefghijklmnopqrstuvwxyz123456

4.2 更新 Azure 重定向 URI

  1. 返回 Azure Portal 的应用注册页面
  2. 进入 “身份验证” (Authentication)
  3. 更新或添加重定向 URI:
    https://abcdefghijklmnopqrstuvwxyz123456.chromiumapp.org/onedrive
    
    • abcdefghijklmnopqrstuvwxyz123456 替换为实际的扩展 ID
  4. 点击 “保存” (Save)

4.3 在代码中配置 Client ID

以 QuickFillForm 插件为例,在 onedrive-sync.js 文件中配置:

class OneDriveSync {
    constructor() {
        // 替换为你的 Azure 应用程序 ID (Client ID)
        this.clientId = '12345678-1234-1234-1234-123456789abc';
        
        // 获取当前插件的 ID
        this.redirectUri = `https://${chrome.runtime.id}.chromiumapp.org/onedrive`;
        
        // Microsoft Graph API 端点
        this.graphEndpoint = 'https://graph.microsoft.com/v1.0';
        
        // 应用专用文件夹路径
        this.appFolderPath = '/me/drive/special/approot';
        
        // 请求的权限范围
        this.scopes = [
            'Files.ReadWrite.AppFolder',
            'offline_access'
        ];
    }
    
    // 获取访问令牌
    async getAccessToken() {
        return new Promise((resolve, reject) => {
            chrome.identity.launchWebAuthFlow({
                url: this.getAuthUrl(),
                interactive: true
            }, (redirectUrl) => {
                if (chrome.runtime.lastError) {
                    reject(chrome.runtime.lastError);
                    return;
                }
                
                // 从回调 URL 中提取 access_token
                const url = new URL(redirectUrl);
                const token = url.hash.match(/access_token=([^&]*)/)?.[1];
                
                if (token) {
                    resolve(token);
                } else {
                    reject(new Error('无法获取访问令牌'));
                }
            });
        });
    }
    
    // 构建授权 URL
    getAuthUrl() {
        const params = new URLSearchParams({
            client_id: this.clientId,
            response_type: 'token',
            redirect_uri: this.redirectUri,
            scope: this.scopes.join(' '),
            response_mode: 'fragment'
        });
        
        return `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?${params}`;
    }
    
    // 上传文件到 OneDrive
    async uploadFile(fileName, content) {
        const token = await this.getAccessToken();
        
        const response = await fetch(
            `${this.graphEndpoint}${this.appFolderPath}:/${fileName}:/content`,
            {
                method: 'PUT',
                headers: {
                    'Authorization': `Bearer ${token}`,
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(content)
            }
        );
        
        if (!response.ok) {
            throw new Error(`上传失败: ${response.statusText}`);
        }
        
        return await response.json();
    }
    
    // 从 OneDrive 下载文件
    async downloadFile(fileName) {
        const token = await this.getAccessToken();
        
        const response = await fetch(
            `${this.graphEndpoint}${this.appFolderPath}:/${fileName}:/content`,
            {
                headers: {
                    'Authorization': `Bearer ${token}`
                }
            }
        );
        
        if (!response.ok) {
            throw new Error(`下载失败: ${response.statusText}`);
        }
        
        return await response.json();
    }
}

// 使用示例
const onedriveSync = new OneDriveSync();

// 上传数据
onedriveSync.uploadFile('data.json', { key: 'value' })
    .then(() => console.log('上传成功'))
    .catch(err => console.error('上传失败:', err));

// 下载数据
onedriveSync.downloadFile('data.json')
    .then(data => console.log('下载的数据:', data))
    .catch(err => console.error('下载失败:', err));

4.4 Web 应用配置示例

对于传统 Web 应用(有后端),使用授权码流程更安全:

// 前端代码
function loginWithOneDrive() {
    const clientId = 'YOUR_CLIENT_ID';
    const redirectUri = 'https://yourdomain.com/callback';
    const scopes = 'Files.ReadWrite.AppFolder offline_access';
    
    const authUrl = `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?` +
        `client_id=${clientId}` +
        `&response_type=code` +
        `&redirect_uri=${encodeURIComponent(redirectUri)}` +
        `&scope=${encodeURIComponent(scopes)}`;
    
    window.location.href = authUrl;
}

// 后端代码 (Node.js Express 示例)
app.get('/callback', async (req, res) => {
    const code = req.query.code;
    
    // 用授权码换取访问令牌
    const tokenResponse = await fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: new URLSearchParams({
            client_id: process.env.CLIENT_ID,
            client_secret: process.env.CLIENT_SECRET,
            code: code,
            redirect_uri: 'https://yourdomain.com/callback',
            grant_type: 'authorization_code'
        })
    });
    
    const tokenData = await tokenResponse.json();
    
    // 保存 access_token 和 refresh_token
    req.session.accessToken = tokenData.access_token;
    req.session.refreshToken = tokenData.refresh_token;
    
    res.redirect('/dashboard');
});

4.5 配置 manifest.json (浏览器插件)

在插件的 manifest.json 中添加必要的权限:

{
    "manifest_version": 3,
    "name": "QuickFillForm",
    "version": "1.0.0",
    "permissions": [
        "identity",
        "storage"
    ],
    "host_permissions": [
        "https://login.microsoftonline.com/*",
        "https://graph.microsoft.com/*"
    ],
    "oauth2": {
        "client_id": "12345678-1234-1234-1234-123456789abc.chromiumapp.org",
        "scopes": [
            "Files.ReadWrite.AppFolder",
            "offline_access"
        ]
    }
}

第五步: 测试同步功能

5.1 测试用户认证

  1. 在应用中触发 OneDrive 登录流程
  2. 浏览器会打开 Microsoft 登录页面
  3. 输入 Microsoft 账号和密码登录
  4. 首次使用会看到权限请求页面:
    • 显示应用名称
    • 列出请求的权限
    • 显示应用发布者信息
  5. 点击 “接受” (Accept) 按钮授权

5.2 测试文件上传

浏览器插件示例:

// 测试上传功能
async function testUpload() {
    const testData = {
        name: '张三',
        email: 'zhangsan@example.com',
        phone: '13800138000'
    };
    
    try {
        const result = await onedriveSync.uploadFile('test-data.json', testData);
        console.log('✅ 上传成功:', result);
    } catch (error) {
        console.error('❌ 上传失败:', error);
    }
}

testUpload();

5.3 测试文件下载

// 测试下载功能
async function testDownload() {
    try {
        const data = await onedriveSync.downloadFile('test-data.json');
        console.log('✅ 下载成功:', data);
    } catch (error) {
        console.error('❌ 下载失败:', error);
    }
}

testDownload();

5.4 验证文件存储位置

  1. 登录 OneDrive 网页版
  2. 在左侧菜单中找到 “应用” (Apps) 文件夹
  3. 进入你的应用文件夹,例如 Apps/QuickFillForm/
  4. 确认文件已成功上传

5.5 测试跨设备同步

  1. 在设备 A 上传数据
  2. 在设备 B 上登录同一 Microsoft 账号
  3. 下载数据,验证是否与设备 A 一致
  4. 修改数据后再次上传
  5. 在设备 A 上下载,验证是否更新

常见问题与解决方案

Q1: 无法获取访问令牌

错误信息:

AADSTS50011: The redirect URI specified in the request does not match

原因分析:

  • Client ID 配置错误
  • 重定向 URI 不匹配
  • Extension ID 更改后未更新 Azure 配置

解决方案:

  1. 检查代码中的 clientId 是否正确复制

    // 错误: 多余的空格或特殊字符
    this.clientId = '12345678-1234-1234-1234-123456789abc ';
    
    // 正确
    this.clientId = '12345678-1234-1234-1234-123456789abc';
    
  2. 确认 Azure 应用注册中的重定向 URI 与代码完全一致

    • Azure 配置: https://abcdefg123456.chromiumapp.org/onedrive
    • 代码配置: https://abcdefg123456.chromiumapp.org/onedrive
    • 注意区分大小写和尾部斜杠
  3. 保存配置后等待 2-5 分钟让配置生效

Q2: 权限被拒绝,无法读写文件

错误信息:

403 Forbidden: Access denied

原因分析:

  • 权限未正确添加
  • 用户未授权
  • 使用了错误的 API 端点

解决方案:

  1. 在 Azure Portal 检查是否已添加 Files.ReadWrite.AppFolder 权限
  2. 清除用户授权,重新登录并同意权限
    // 清除缓存的令牌
    chrome.identity.clearAllCachedAuthTokens(() => {
        console.log('已清除缓存,请重新登录');
    });
    
  3. 确认使用正确的 API 路径:
    // 正确: 使用应用专用文件夹
    const path = '/me/drive/special/approot:/data.json:/content';
    
    // 错误: 使用根目录 (需要 Files.ReadWrite.All 权限)
    const path = '/me/drive/root:/data.json:/content';
    

Q3: 访问令牌过期

错误信息:

401 Unauthorized: The access token has expired

原因分析:

  • Access Token 默认有效期 1 小时
  • 未实现 Refresh Token 自动刷新机制

解决方案:

实现令牌刷新逻辑:

class OneDriveSync {
    constructor() {
        this.accessToken = null;
        this.refreshToken = null;
        this.tokenExpiry = null;
    }
    
    // 获取有效的访问令牌
    async getValidAccessToken() {
        // 如果令牌未过期,直接返回
        if (this.accessToken && this.tokenExpiry > Date.now()) {
            return this.accessToken;
        }
        
        // 如果有刷新令牌,尝试刷新
        if (this.refreshToken) {
            return await this.refreshAccessToken();
        }
        
        // 否则重新登录
        return await this.getAccessToken();
    }
    
    // 刷新访问令牌
    async refreshAccessToken() {
        const response = await fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            body: new URLSearchParams({
                client_id: this.clientId,
                refresh_token: this.refreshToken,
                grant_type: 'refresh_token'
            })
        });
        
        const data = await response.json();
        
        this.accessToken = data.access_token;
        this.refreshToken = data.refresh_token;
        this.tokenExpiry = Date.now() + (data.expires_in * 1000);
        
        // 保存到本地存储
        chrome.storage.local.set({
            onedrive_access_token: this.accessToken,
            onedrive_refresh_token: this.refreshToken,
            onedrive_token_expiry: this.tokenExpiry
        });
        
        return this.accessToken;
    }
}

Q4: 找不到扩展 ID

解决方案:

  1. 访问浏览器的扩展管理页面:
    • Chrome: chrome://extensions/
    • Edge: edge://extensions/
  2. 确保已开启"开发者模式"
  3. Extension ID 显示在扩展卡片上,格式类似:
    ID: abcdefghijklmnopqrstuvwxyz123456
    
  4. 如果是已发布的扩展,可以在 Chrome Web Store 的 URL 中找到:
    https://chrome.google.com/webstore/detail/extension-name/abcdefghijklmnopqrstuvwxyz123456
    

Q5: 文件上传成功但在 OneDrive 中找不到

原因分析:

  • 使用了应用专用文件夹,路径在 Apps/<AppName>/
  • OneDrive 网页版界面可能有延迟
  • 文件名或路径错误

解决方案:

  1. 在 OneDrive 网页版中,导航到:
    OneDrive → 应用 (Apps) → QuickFillForm
    
  2. 等待 1-2 分钟,刷新页面
  3. 使用 API 查询文件是否存在:
    // 列出应用文件夹中的所有文件
    async function listFiles() {
        const token = await onedriveSync.getValidAccessToken();
    
        const response = await fetch(
            'https://graph.microsoft.com/v1.0/me/drive/special/approot/children',
            {
                headers: { 'Authorization': `Bearer ${token}` }
            }
        );
    
        const data = await response.json();
        console.log('文件列表:', data.value);
    }
    

Q6: 跨域请求被阻止 (CORS)

错误信息:

Access to fetch at 'https://graph.microsoft.com/...' has been blocked by CORS policy

原因分析:

  • 在普通网页中直接请求 Graph API
  • 未在 manifest.json 中配置 host_permissions

解决方案:

方案一: 使用后端代理 (推荐)

// 前端调用后端 API
fetch('/api/onedrive/upload', {
    method: 'POST',
    body: JSON.stringify(data)
});

// 后端转发到 Microsoft Graph
app.post('/api/onedrive/upload', async (req, res) => {
    const response = await fetch('https://graph.microsoft.com/v1.0/...', {
        headers: { 'Authorization': `Bearer ${accessToken}` }
    });
    // ...
});

方案二: 浏览器插件配置

{
    "host_permissions": [
        "https://login.microsoftonline.com/*",
        "https://graph.microsoft.com/*"
    ]
}

Q7: 授权页面显示"需要管理员审批"

错误信息:

AADSTS65001: The user or administrator has not consented to use the application

原因分析:

  • 组织策略要求管理员批准应用
  • 请求的权限需要管理员同意

解决方案:

  1. 如果你是管理员,在 Azure Portal 中授予管理员同意
  2. 如果不是管理员,联系 IT 部门批准应用
  3. 使用个人 Microsoft 账号测试(个人账号无需管理员同意)
  4. 减少权限请求,只使用委托权限而不是应用权限

安全最佳实践

1. 保护 Client Secret

// ❌ 错误: 在前端代码中硬编码密钥
const clientSecret = 'your-secret-key-123456';

// ✅ 正确: 在后端使用环境变量
const clientSecret = process.env.AZURE_CLIENT_SECRET;

2. 使用最小权限原则

// ❌ 错误: 请求过多权限
const scopes = [
    'Files.ReadWrite.All',
    'Mail.Read',
    'Contacts.ReadWrite'
];

// ✅ 正确: 只请求必需的权限
const scopes = [
    'Files.ReadWrite.AppFolder',
    'offline_access'
];

3. 安全存储令牌

// ✅ 浏览器插件: 使用 chrome.storage.local
chrome.storage.local.set({
    onedrive_token: encryptedToken  // 最好加密存储
});

// ✅ Web 应用: 使用 HttpOnly Cookie
res.cookie('access_token', token, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict'
});

4. 定期检查应用活动

  1. 登录 Azure Portal
  2. 进入应用注册 → 你的应用
  3. 查看 “概述” 中的使用情况统计
  4. 检查异常登录和 API 调用

5. 处理敏感数据

// 上传前加密敏感数据
async function uploadSecureData(data) {
    const encrypted = await encryptData(data);
    await onedriveSync.uploadFile('secure-data.enc', encrypted);
}

// 下载后解密
async function downloadSecureData() {
    const encrypted = await onedriveSync.downloadFile('secure-data.enc');
    return await decryptData(encrypted);
}

进阶技巧

1. 实现文件版本管理

OneDrive 自动保留文件版本历史,可以通过 API 访问:

async function getFileVersions(fileName) {
    const token = await onedriveSync.getValidAccessToken();
    
    const response = await fetch(
        `https://graph.microsoft.com/v1.0/me/drive/special/approot:/${fileName}:/versions`,
        {
            headers: { 'Authorization': `Bearer ${token}` }
        }
    );
    
    const data = await response.json();
    return data.value;  // 返回版本列表
}

2. 实现增量同步

通过 Delta API 获取文件变化:

async function syncChanges(deltaLink) {
    const token = await onedriveSync.getValidAccessToken();
    
    const url = deltaLink || 
        'https://graph.microsoft.com/v1.0/me/drive/special/approot/delta';
    
    const response = await fetch(url, {
        headers: { 'Authorization': `Bearer ${token}` }
    });
    
    const data = await response.json();
    
    // 处理变化的文件
    for (const item of data.value) {
        if (item.deleted) {
            console.log('文件已删除:', item.name);
        } else {
            console.log('文件已更新:', item.name);
        }
    }
    
    // 保存 deltaLink 供下次使用
    return data['@odata.deltaLink'];
}

3. 实现冲突解决

async function smartSync(fileName, localData) {
    try {
        // 下载远程文件
        const remoteData = await onedriveSync.downloadFile(fileName);
        
        // 比较时间戳
        if (remoteData.lastModified > localData.lastModified) {
            // 远程更新,下载覆盖本地
            return { action: 'download', data: remoteData };
        } else if (localData.lastModified > remoteData.lastModified) {
            // 本地更新,上传覆盖远程
            await onedriveSync.uploadFile(fileName, localData);
            return { action: 'upload', data: localData };
        } else {
            // 智能合并
            const merged = mergeData(localData, remoteData);
            await onedriveSync.uploadFile(fileName, merged);
            return { action: 'merge', data: merged };
        }
    } catch (error) {
        // 文件不存在,直接上传
        if (error.status === 404) {
            await onedriveSync.uploadFile(fileName, localData);
            return { action: 'create', data: localData };
        }
        throw error;
    }
}

4. 批量操作

使用批处理 API 提高性能:

async function batchUpload(files) {
    const token = await onedriveSync.getValidAccessToken();
    
    const requests = files.map((file, index) => ({
        id: index.toString(),
        method: 'PUT',
        url: `/me/drive/special/approot:/${file.name}:/content`,
        headers: {
            'Content-Type': 'application/json'
        },
        body: file.content
    }));
    
    const response = await fetch('https://graph.microsoft.com/v1.0/$batch', {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({ requests })
    });
    
    return await response.json();
}

相关资源

官方文档

开发工具

示例项目

社区资源