You need to enable JavaScript to run this app. 基于Microsoft Graph提取Bookings联系人OData ID实现批量清理
优惠活动
大模型
产品
解决方案
定价
更多

基于Microsoft Graph提取Bookings联系人OData ID实现批量清理

业务实现背景
  • 日常使用Bookings服务承接联系人预约流程时,系统会自动将相关联系人信息同步存储到无需使用的Bookings客户数据库,需要定期清理这类冗余数据
  • 当前开发进度:已完成带身份令牌的GET/DELETE请求封装、身份认证流程打通,暂未实现从GET接口返回的OData格式结果中提取客户ID、循环发起删除请求的逻辑
  • 现有基础配置:
    • 拉取客户列表的GET端点:https://graph.microsoft.com/beta/bookingBusinesses/MallLocation@acme.ca/customers/?$select=id&$top=1
    • 已封装的两个请求方法:GetHttpContentWithToken(携带Bearer令牌发送GET请求)、DeleteHttpContentWithToken(携带Bearer令牌发送DELETE请求)
核心实现步骤

步骤1:定义OData返回的序列化模型

不要通过正则表达式硬抠返回字符串里的ID,Graph返回的OData结构是固定的,直接用强类型反序列化可以避免转义字符、格式变动导致的解析错误,需要提前定义两个对应的数据结构:

/// <summary>
/// Graph OData列表返回的通用基础结构
/// </summary>
public class GraphODataListResponse<T>
{
    /// <summary>
    /// 列表条目存在value字段中
    /// </summary>
    public List<T> Value { get; set; }

    /// <summary>
    /// 分页下一页地址,对应OData原生字段@odata.nextLink
    /// </summary>
    [System.Text.Json.Serialization.JsonPropertyName("@odata.nextLink")]
    public string OdataNextLink { get; set; }
}

/// <summary>
/// 仅拉取id字段的Bookings客户条目结构
/// </summary>
public class BookingCustomerEntry
{
    public string Id { get; set; }
}

步骤2:构造单个客户的DELETE请求地址

Graph删除单个Bookings客户的端点规则为业务主体地址拼接对应客户ID,格式如下:

删除端点基础格式:https://graph.microsoft.com/beta/bookingBusinesses/{对应Bookings业务标识}/customers/{目标客户ID}
举例:如果从列表中拿到的客户ID为9f8e7d6c-5b4a,对应的DELETE地址就是把该ID拼接在customers路径末尾,不需要携带额外查询参数。

步骤3:实现循环遍历删除逻辑

循环逻辑需要覆盖两个场景:一是遍历当前返回页的所有客户ID逐个发送删除请求,二是处理分页拉取直到没有更多待删数据。这里要注意避免触发Graph接口的限流规则,不要高并发批量发请求,单线程顺序删除即可。
把之前认证完成后的调用逻辑替换为如下实现:

if (authResult != null)
{
    // 正式清理时将测试用的$top=1调整为单页最大支持的$top=999,减少请求次数
    var currentListEndpoint = graphAPIEndpoint.Replace("$top=1", "$top=999");
    bool hasRemainingData = true;

    while (hasRemainingData)
    {
        // 拉取当前分页的客户列表
        var odataResponseContent = await GetHttpContentWithToken(currentListEndpoint, authResult.AccessToken);
        // 反序列化拿到当前页的客户ID列表
        var listResult = System.Text.Json.JsonSerializer.Deserialize<GraphODataListResponse<BookingCustomerEntry>>(odataResponseContent);

        // 边界判断:如果当前页没有可删数据,直接退出循环
        if (listResult?.Value == null || listResult.Value.Count == 0)
        {
            hasRemainingData = false;
            break;
        }

        // 遍历当前页每个客户ID,逐个发起删除
        foreach (var customer in listResult.Value)
        {
            // 构造对应客户的DELETE请求地址
            var deleteTargetEndpoint = $"https://graph.microsoft.com/beta/bookingBusinesses/MallLocation@acme.ca/customers/{customer.Id}";
            // 发起删除请求,可在此处记录删除结果日志
            var deleteProcessResult = await DeleteHttpContentWithToken(deleteTargetEndpoint, authResult.AccessToken);
        }

        // 处理分页:如果返回结果中存在下一页地址,更新请求地址后继续循环
        if (!string.IsNullOrEmpty(listResult.OdataNextLink))
        {
            currentListEndpoint = listResult.OdataNextLink;
        }
        else
        {
            hasRemainingData = false;
        }
    }

    // 清理完成后更新界面展示
    ResultText.Text = "冗余Bookings客户数据清理完成";
    DisplayBasicTokenInfo(authResult);
    this.SignOutButton.Visibility = Visibility.Visible;
}
常见踩坑提醒
  • 原有代码配置的权限范围user.read不足以调用Bookings客户的读写接口,需要补充对应的Bookings管理权限(如Bookings.Manage.All),否则请求会返回403权限不足
  • 长时间运行清理任务时,不要在每次请求中实例化新的HttpClient,容易占满系统套接字资源,建议将HttpClient改为单例复用
  • 单个客户删除失败时要加异常捕获,不要让局部错误中断整个清理流程,可将失败的ID单独记录后后续重试

内容的提问来源于stack exchange,提问作者Mark Smed

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.06 08:45:04