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

如何通过TypeScript调用Azure DevOps REST API获取指定项目的所有用户列表

如何通过TypeScript调用Azure DevOps REST API获取指定项目的所有用户列表

嗨,我来帮你搞定这个问题!你说得没错,目前Azure DevOps的扩展API里确实没有直接的listUsers(projectId)方法,但其实有两种靠谱的方式实现获取指定项目所有用户的需求,咱们一步步来:

一、使用官方azure-devops-extension-api客户端实现(推荐)

其实你可能没注意到,azure-devops-extension-api里已经提供了相关的客户端方法,咱们可以通过项目的内置安全组来间接获取所有成员——因为每个项目都会自动创建一个「Project Valid Users」组,所有项目成员(包括直接添加和通过团队加入的)都会在这个组里。

具体步骤:

  1. 获取项目内置安全组的ID
    先调用GraphRestClient的方法找到项目对应的「Project Valid Users」组:

    import { GraphRestClient, GraphGroup } from "azure-devops-extension-api/Graph";
    import { getClient } from "azure-devops-extension-api";
    
    async function getProjectValidUsersGroupId(projectId: string): Promise<string> {
      const graphClient = getClient(GraphRestClient);
      // 过滤出项目的内置安全组
      const groups = await graphClient.getGroups({
        projectId: projectId,
        filter: "groupType eq 'ProjectSecurity'"
      });
      // 找到「Project Valid Users」组
      const validUsersGroup = groups.find(group => group.displayName.includes("Valid Users"));
      if (!validUsersGroup) {
        throw new Error("未找到项目的Valid Users安全组");
      }
      return validUsersGroup.descriptor!;
    }
    
  2. 获取安全组的所有成员
    用MembershipRestClient获取组内成员,同时递归展开子组(避免遗漏通过嵌套组加入的用户):

    import { MembershipRestClient, GraphMember } from "azure-devops-extension-api/Graph";
    
    async function getGroupMembers(groupDescriptor: string, graphClient: GraphRestClient): Promise<GraphMember[]> {
      const membershipClient = getClient(MembershipRestClient);
      // 获取组的直接成员
      const memberships = await membershipClient.getMemberships(groupDescriptor, "direct");
      const members: GraphMember[] = [];
    
      for (const membership of Object.values(memberships)) {
        if (membership.memberDescriptor) {
          const member = await graphClient.getMember(membership.memberDescriptor);
          // 区分用户和子组,子组递归处理
          if (member.subjectKind === "user") {
            members.push(member);
          } else if (member.subjectKind === "group") {
            const nestedMembers = await getGroupMembers(member.descriptor!, graphClient);
            members.push(...nestedMembers);
          }
        }
      }
      return members;
    }
    
  3. 整合完整的业务函数
    把上面的方法组合起来,就是你需要的getAllProjectMembers:

    export async function getAllProjectMembers(): Promise<GraphMember[]> {
      const projectId = await getCurrentProjectId(); // 你已有的获取当前项目ID的方法
      const graphClient = getClient(GraphRestClient);
      const validUsersGroupId = await getProjectValidUsersGroupId(projectId);
      const projectMembers = await getGroupMembers(validUsersGroupId, graphClient);
      // 去重,避免同一用户通过多个组加入的重复情况
      const uniqueMembers = Array.from(new Map(projectMembers.map(m => [m.descriptor, m])).values());
      return uniqueMembers;
    }
    

二、直接构造HTTP请求实现(适合特殊场景)

如果官方客户端的方法在你的API版本里有差异,或者你更倾向于直接调用REST API,也可以用TypeScript的fetch方法手动实现:

import { getCurrentProjectId } from "./your-utils"; // 替换成你的工具方法路径

export async function getAllProjectMembers(): Promise<any[]> {
  const projectId = await getCurrentProjectId();
  const accessToken = await VSS.getAccessToken();
  const orgUrl = VSS.getWebContext().collection.uri;

  // 第一步:获取项目的Valid Users组
  const groupsUrl = `${orgUrl}_apis/graph/groups?api-version=7.1-preview.1&projectId=${projectId}&$filter=groupType eq 'ProjectSecurity'`;
  const groupsResponse = await fetch(groupsUrl, {
    headers: {
      "Authorization": `Bearer ${accessToken}`,
      "Content-Type": "application/json"
    }
  });
  const groupsData = await groupsResponse.json();
  const validUsersGroup = groupsData.value.find((group: any) => group.displayName.includes("Valid Users"));
  if (!validUsersGroup) {
    throw new Error("未找到项目的Valid Users安全组");
  }
  const groupDescriptor = validUsersGroup.descriptor;

  // 第二步:递归获取组内所有成员
  async function fetchGroupMembers(descriptor: string): Promise<any[]> {
    const membersUrl = `${orgUrl}_apis/graph/memberships/${descriptor}?api-version=7.1-preview.1&direction=down`;
    const membersResponse = await fetch(membersUrl, {
      headers: {
        "Authorization": `Bearer ${accessToken}`,
        "Content-Type": "application/json"
      }
    });
    const membersData = await membersResponse.json();
    const members: any[] = [];

    for (const membership of Object.values(membersData.value)) {
      const memberDescriptor = (membership as any).memberDescriptor;
      // 获取成员详情
      const memberUrl = `${orgUrl}_apis/graph/users/${memberDescriptor}?api-version=7.1-preview.1`;
      const memberResponse = await fetch(memberUrl, {
        headers: { "Authorization": `Bearer ${accessToken}` }
      });
      const member = await memberResponse.json();

      if (member.subjectKind === "user") {
        members.push(member);
      } else if (member.subjectKind === "group") {
        const nestedMembers = await fetchGroupMembers(member.descriptor);
        members.push(...nestedMembers);
      }
    }
    return members;
  }

  const allMembers = await fetchGroupMembers(groupDescriptor);
  // 去重处理
  const uniqueMembers = Array.from(new Map(allMembers.map(m => [m.descriptor, m])).values());
  return uniqueMembers;
}

补充说明:

为什么不直接调用组织级的用户列表API?因为Azure DevOps的用户是组织维度的,项目成员本质是通过安全组关联的,通过项目内置组获取的成员列表是最准确的,能覆盖所有直接或间接加入项目的用户。如果只需要某个团队的成员,替换成团队对应的安全组即可,逻辑完全一致。

备注:内容来源于stack exchange,提问作者Tomasz Małachowski

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.13 18:19:37