{"version":3,"sources":["../src/index.ts","../src/authnpaas.module.ts","../src/const.ts","../src/guards/authnpaas.guard.ts","../src/services/authnpaas.service.ts","../src/controllers/authnpaas.controller.ts","../src/decorators/public.decorator.ts","../src/decorators/need-login.decorator.ts"],"sourcesContent":["/**\n * @lark-apaas/nestjs-authnpaas\n *\n * 基于 CASL 的 NestJS 权限管理模块\n * 提供角色、权限点位、环境等多维度的鉴权能力\n */\n\n// 主模块\nexport { AuthNPaasModule } from './authnpaas.module';\n\n// 服务\nexport * from './services';\n\n// 控制器\nexport * from './controllers';\n\n// 装饰器\nexport * from './decorators';\n\n// 守卫\nexport * from './guards';\n\n// 类型定义\nexport * from './types';\n\n// 常量\nexport * from './const';\n","import { DynamicModule, Module } from '@nestjs/common';\nimport { APP_GUARD, Reflector } from '@nestjs/core';\nimport { AuthNPaasModuleOptions } from './types';\nimport { AUTHNPAAS_MODULE_OPTIONS } from './const';\nimport { AuthNPaasGuard } from './guards/authnpaas.guard';\nimport { AuthNPaasService } from './services/authnpaas.service';\nimport { AuthNPaasController } from './controllers/authnpaas.controller';\n\n@Module({})\nexport class AuthNPaasModule {\n  static forRoot(options?: AuthNPaasModuleOptions): DynamicModule {\n    return {\n      module: AuthNPaasModule,\n      global: true,\n      controllers: [AuthNPaasController],\n      providers: [\n        // 配置提供者\n        {\n          provide: AUTHNPAAS_MODULE_OPTIONS,\n          useValue: {\n            ...(options || {}),\n          },\n        },\n        // 核心服务\n        Reflector,\n        // 服务提供者\n        AuthNPaasGuard,\n        AuthNPaasService,\n        // 守卫提供者\n        {\n          provide: APP_GUARD,\n          useClass: AuthNPaasGuard,\n        },\n      ],\n      exports: [AuthNPaasService],\n    };\n  }\n}\n","/**\n * 常量\n */\n\n/** AuthNPaas 模块选项 Token */\nexport const AUTHNPAAS_MODULE_OPTIONS = Symbol('AUTHNPAAS_MODULE_OPTIONS');\n\n/**\n * 元数据键\n */\n\n/** 需要登录元数据键 */\nexport const NEED_LOGIN_KEY = 'authnpaas:needLogin';\n","import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';\nimport { Response } from 'express';\nimport { Reflector } from '@nestjs/core';\nimport { NEED_LOGIN_KEY } from '../const';\n\n/**\n * AuthNPaas 守卫\n * 负责协调所有鉴权检查，具体检查逻辑委托给 PermissionService\n */\n@Injectable()\nexport class AuthNPaasGuard implements CanActivate {\n  constructor(private reflector: Reflector) {}\n\n  async canActivate(context: ExecutionContext): Promise<boolean> {\n    const http = context.switchToHttp();\n    const request = http.getRequest();\n    const response = http.getResponse<Response>();\n\n    const { userId, loginUrl } = request.userContext || {};\n\n    // 读取 NeedLogin 元数据并标记到请求对象，供异常过滤器使用\n    const needLoginMeta = this.reflector.getAllAndOverride<{\n      loginPath?: string;\n    }>(NEED_LOGIN_KEY, [context.getHandler(), context.getClass()]);\n\n    // NeedLogin 且无 userId -> 一律 401；拿得到 loginUrl 时顺带透出跳转 url\n    if (needLoginMeta && !userId) {\n      if (loginUrl) {\n        response.setHeader('x-login-url', loginUrl);\n      }\n      throw new UnauthorizedException('未登录');\n    }\n\n    return true;\n  }\n}\n","import { Injectable, Inject, Logger, HttpException, HttpStatus } from '@nestjs/common';\nimport {\n  PLATFORM_HTTP_CLIENT,\n  OBSERVABLE_SERVICE,\n  type PlatformHttpClient,\n  type ObservableService,\n  RequestContextService,\n  createTimer,\n} from '@lark-apaas/nestjs-common';\n\nimport type { FeishuIdConvertOptions, MiaodaUserInfo } from '../types';\n\nconst SDK_MODULE = 'authn';\n\n/**\n * 平台 InnerAPI「按 ID 批量查人」路径。\n *\n * 服务端路由为 `/innerapi/v1/app/:appID/account/list_users`（另有\n * `/force_innerapi/...` 别名），`/innerapi` 前缀在 PlatformHttpClient 的\n * baseURL 里（env `FORCE_AUTHN_INNERAPI_DOMAIN`），故此处只写相对部分，\n * 与同包 `account/user/convert` 的写法一致。\n *\n * 刻意不做成 `AuthNPaasModuleOptions` 的可覆盖字段——module options 是公开\n * API，加了就删不掉，而这里一旦要改只需发一个补丁版。\n */\nconst LIST_USERS_PATH = (appId: string): string => `/v1/app/${appId}/account/list_users`;\n\n/** 单次批量查询的 ID 上限，与 getBatchLarkUserIds / getBatchMiaodaUserIds 一致 */\nconst MAX_BATCH_SIZE = 100;\n\n/** RPC 层的 BaseResp。注意下游异常时 StatusCode 仍是 0，真正的错误在 KStatusCode 上 */\ninterface PlatformBaseResp {\n  StatusCode?: number;\n  StatusMessage?: string;\n  KStatusCode?: string;\n  KStatusMessage?: string;\n}\n\n/** 业务负载。网关可能把它整个包进 data，也可能摊在顶层，两种都要吃 */\ninterface ListUsersPayload {\n  items?: MiaodaUserInfo[];\n  status_code?: string | number;\n  error_msg?: string;\n  /** 部分服务端用 err_msg 而非 error_msg，两者都读，否则错误文案会被整段丢弃 */\n  err_msg?: string;\n  BaseResp?: PlatformBaseResp;\n}\n\n/** 平台响应体（宽松形态） */\ninterface ListUsersResponse extends ListUsersPayload {\n  data?: ListUsersPayload;\n}\n\nfunction throwPlatformError(\n  message: string,\n  httpStatus: number,\n  platformCode?: string | number,\n  logId?: string,\n): never {\n  throw new HttpException(\n    {\n      statusCode: httpStatus,\n      code: 'PLATFORM_API_ERROR',\n      message,\n      platformCode,\n      'x-tt-logid': logId ?? '',\n    },\n    httpStatus,\n  );\n}\n\n/**\n * 取平台错误文案，兼容 error_msg / err_msg 两种字段名、顶层与 data 两层位置。\n *\n * 与 nestjs-authzpaas 的 `pickErrorMessage` 同形：部分服务端用 err_msg 而非\n * error_msg，只读一个会把错误文案整段丢掉、退化成兜底文案，排查只剩 logid。\n */\nfunction pickPlatformMessage(body: ListUsersResponse | undefined, fallback: string): string {\n  return (\n    body?.error_msg\n    || body?.err_msg\n    || body?.data?.error_msg\n    || body?.data?.err_msg\n    || fallback\n  );\n}\n\n/** 只透出契约内的三个字段，防止平台将来多返回字段时被动扩大对外契约 */\nfunction pickUserInfo(raw: MiaodaUserInfo): MiaodaUserInfo {\n  return {\n    miaodaUserID: String(raw.miaodaUserID),\n    name: raw.name,\n    avatar: raw.avatar,\n  };\n}\n\n/**\n * 妙搭通讯录能力（`AuthNPaasService.listUsersByIds`，服务端 / OpenAPI 态可用）\n *\n * 与本类其他方法的区别：查人**不依赖调用者身份**，只用应用凭据（AK/SK 自签\n * JWT + x-api-key）+ 上下文里的 `appId`。因此在 OpenAPI 请求（没有 cookie、\n * 网关注入不了登录态、`RequestContext.userId` 为空）下同样可用。\n *\n * 服务端**没有**搜人 / 搜部门 / 搜群能力——搜索结果取决于「谁在搜」，\n * OpenAPI 态拿不到调用者视角。需要搜索请改走飞书开放平台通讯录 API。\n */\n\n/**\n * 妙搭用户ID → 飞书 user_id（租户内 Employee ID，平台枚举名 ForceUserID2LarkExternalID）\n */\nconst CONVERT_TYPE_FORCE_TO_FEISHU = 30;\n\n/**\n * 飞书 user_id（租户内 Employee ID）→ 妙搭用户ID（平台枚举名 LarkExternalID2ForceUserID）\n */\nconst CONVERT_TYPE_FEISHU_TO_FORCE = 31;\n\n/**\n * 妙搭用户ID → LarkID（飞书全局用户 ID，平台枚举名 ForceUserID2LarkUserID）\n */\nconst CONVERT_TYPE_FORCE_TO_LARK_ID = 40;\n\n/**\n * LarkID（飞书全局用户 ID）→ 妙搭用户ID（平台枚举名 LarkUserID2ForceUserID）\n */\nconst CONVERT_TYPE_LARK_ID_TO_FORCE = 41;\n\n/**\n * 妙搭用户ID → 飞书 open_id / union_id（平台枚举名 ForceUserID2FeishuOpenID / ForceUserID2FeishuUnionID）\n */\nconst CONVERT_TYPE_FORCE_TO_OPEN_ID = 10;\nconst CONVERT_TYPE_FORCE_TO_UNION_ID = 11;\n\n/**\n * 飞书 open_id / union_id → 妙搭用户ID（平台枚举名 FeishuOpenID2ForceUserID / FeishuUnionID2ForceUserID）\n */\nconst CONVERT_TYPE_OPEN_ID_TO_FORCE = 20;\nconst CONVERT_TYPE_UNION_ID_TO_FORCE = 21;\n\n/**\n * ID 转换结果项\n */\nexport interface UserIDMappingItem {\n  sourceUserID: string;\n  targetUserID: string;\n}\n\n/**\n * 批量转换响应\n */\ninterface BatchConvertResponse {\n  data?: {\n    items?: UserIDMappingItem[];\n  };\n  status_code?: string;\n  status_message?: string;\n  error_msg?: string;\n}\n\n@Injectable()\nexport class AuthNPaasService {\n  private readonly logger = new Logger(AuthNPaasService.name);\n\n  constructor(\n    @Inject(PLATFORM_HTTP_CLIENT) private readonly httpClient: PlatformHttpClient,\n    @Inject(OBSERVABLE_SERVICE) private readonly obs: ObservableService,\n    private readonly requestContext: RequestContextService,\n  ) {}\n\n  /**\n   * 获取当前登录用户的飞书 user_id\n   *\n   * 从请求上下文获取当前妙搭 userId，调用转换接口获取对应的飞书 user_id\n   *\n   * @returns 飞书 user_id，如果当前用户未登录或转换失败返回 null\n   */\n  async getCurrentUserLarkUserId(): Promise<string | null> {\n    const userId = this.requestContext.get('userId');\n    if (!userId) {\n      this.logger.warn('getCurrentUserLarkUserId: no userId in request context');\n      return null;\n    }\n\n    const items = await this.batchConvertUserIds('getCurrentUserLarkUserId', [userId], CONVERT_TYPE_FORCE_TO_FEISHU);\n    if (!items || items.length === 0) {\n      return null;\n    }\n\n    return items[0].targetUserID || null;\n  }\n\n  /**\n   * 批量将妙搭 userId 转换为飞书 user_id\n   *\n   * 返回的是租户内 user_id（Employee ID，形如 `18b85ad9`），不是 LarkID；\n   * 需要 LarkID 请用 `getBatchLarkIds`。\n   *\n   * @param userIds - 妙搭用户 ID 列表，最多 100 个\n   * @returns 飞书 user_id 数组，顺序与输入对应，转换失败的项为 null\n   */\n  async getBatchLarkUserIds(userIds: string[]): Promise<(string | null)[]> {\n    return this.convertInOrder('getBatchLarkUserIds', 'userIds', userIds, CONVERT_TYPE_FORCE_TO_FEISHU);\n  }\n\n  /**\n   * 批量将飞书 user_id(employee_id)转换为妙搭 userId\n   *\n   * 入参是租户内 user_id（Employee ID，形如 `18b85ad9`），不是 LarkID；\n   * 手里是 LarkID 请用 `getBatchMiaodaUserIdsByLarkIds`。\n   *\n   * @param employeeIds - 飞书 user_id(employee_id)列表,最多 100 个\n   * @returns 妙搭 userId 数组,顺序与输入对应,转换失败的项为 null\n   */\n  async getBatchMiaodaUserIds(employeeIds: string[]): Promise<(string | null)[]> {\n    return this.convertInOrder('getBatchMiaodaUserIds', 'employeeIds', employeeIds, CONVERT_TYPE_FEISHU_TO_FORCE);\n  }\n\n  /**\n   * 批量将妙搭 userId 转换为 LarkID（飞书全局用户 ID）\n   *\n   * LarkID 是用户在飞书里的全局唯一 ID（形如 `7311528002225963027`），与\n   * `getBatchLarkUserIds` 返回的租户内 user_id（Employee ID，形如 `18b85ad9`）不是同一个 ID。\n   *\n   * @param userIds - 妙搭用户 ID 列表，最多 100 个\n   * @returns LarkID 数组，顺序与输入对应，转换失败的项为 null\n   */\n  async getBatchLarkIds(userIds: string[]): Promise<(string | null)[]> {\n    return this.convertInOrder('getBatchLarkIds', 'userIds', userIds, CONVERT_TYPE_FORCE_TO_LARK_ID);\n  }\n\n  /**\n   * 批量将 LarkID（飞书全局用户 ID）转换为妙搭 userId\n   *\n   * 入参是 LarkID（形如 `7311528002225963027`）；若手里是租户内 user_id（Employee ID），\n   * 请改用 `getBatchMiaodaUserIds`。\n   *\n   * @param larkIds - LarkID 列表，最多 100 个\n   * @returns 妙搭 userId 数组，顺序与输入对应，转换失败的项为 null\n   */\n  async getBatchMiaodaUserIdsByLarkIds(larkIds: string[]): Promise<(string | null)[]> {\n    return this.convertInOrder('getBatchMiaodaUserIdsByLarkIds', 'larkIds', larkIds, CONVERT_TYPE_LARK_ID_TO_FORCE);\n  }\n\n  /**\n   * 批量将妙搭 userId 转换为飞书 open_id（`ou_` 开头）\n   *\n   * open_id 与飞书应用绑定，同一用户在不同应用下的 open_id 不同。要拿到某个飞书应用视角的\n   * open_id，传 `options.feishuClientId`（该应用的 App ID，`cli_` 开头）；不传时平台兜底用妙搭应用。\n   *\n   * @param userIds - 妙搭用户 ID 列表，最多 100 个\n   * @param options - 可选，`feishuClientId` 指定飞书应用\n   * @returns open_id 数组，顺序与输入对应，转换失败的项为 null\n   */\n  async getBatchOpenIds(userIds: string[], options?: FeishuIdConvertOptions): Promise<(string | null)[]> {\n    return this.convertInOrder('getBatchOpenIds', 'userIds', userIds, CONVERT_TYPE_FORCE_TO_OPEN_ID, options);\n  }\n\n  /**\n   * 批量将妙搭 userId 转换为飞书 union_id（`on_` 开头）\n   *\n   * union_id 在同一应用开发商的多个应用间相同。`options.feishuClientId` 用法同 `getBatchOpenIds`。\n   *\n   * @param userIds - 妙搭用户 ID 列表，最多 100 个\n   * @param options - 可选，`feishuClientId` 指定飞书应用\n   * @returns union_id 数组，顺序与输入对应，转换失败的项为 null\n   */\n  async getBatchUnionIds(userIds: string[], options?: FeishuIdConvertOptions): Promise<(string | null)[]> {\n    return this.convertInOrder('getBatchUnionIds', 'userIds', userIds, CONVERT_TYPE_FORCE_TO_UNION_ID, options);\n  }\n\n  /**\n   * 批量将飞书 open_id（`ou_` 开头）转换为妙搭 userId\n   *\n   * open_id 属于哪个飞书应用，就传该应用的 `options.feishuClientId`（`cli_` 开头）；\n   * 不传时平台兜底用妙搭应用，其他应用签发的 open_id 会转换失败。\n   *\n   * @param openIds - open_id 列表，最多 100 个\n   * @param options - 可选，`feishuClientId` 指定 open_id 所属的飞书应用\n   * @returns 妙搭 userId 数组，顺序与输入对应，转换失败的项为 null\n   */\n  async getBatchMiaodaUserIdsByOpenIds(openIds: string[], options?: FeishuIdConvertOptions): Promise<(string | null)[]> {\n    return this.convertInOrder('getBatchMiaodaUserIdsByOpenIds', 'openIds', openIds, CONVERT_TYPE_OPEN_ID_TO_FORCE, options);\n  }\n\n  /**\n   * 批量将飞书 union_id（`on_` 开头）转换为妙搭 userId\n   *\n   * `options.feishuClientId` 用法同 `getBatchMiaodaUserIdsByOpenIds`。\n   *\n   * @param unionIds - union_id 列表，最多 100 个\n   * @param options - 可选，`feishuClientId` 指定 union_id 所属应用开发商下的飞书应用\n   * @returns 妙搭 userId 数组，顺序与输入对应，转换失败的项为 null\n   */\n  async getBatchMiaodaUserIdsByUnionIds(unionIds: string[], options?: FeishuIdConvertOptions): Promise<(string | null)[]> {\n    return this.convertInOrder('getBatchMiaodaUserIdsByUnionIds', 'unionIds', unionIds, CONVERT_TYPE_UNION_ID_TO_FORCE, options);\n  }\n\n  /**\n   * 批量转换并按入参顺序对齐结果：空入参不发请求，超过 100 个抛错，未命中的位置为 null\n   */\n  private async convertInOrder(\n    method: string,\n    paramName: string,\n    ids: string[],\n    convertType: number,\n    options?: FeishuIdConvertOptions,\n  ): Promise<(string | null)[]> {\n    if (!ids || ids.length === 0) {\n      return [];\n    }\n\n    if (ids.length > MAX_BATCH_SIZE) {\n      throw new Error(`${paramName} length must not exceed ${MAX_BATCH_SIZE}`);\n    }\n\n    const items = await this.batchConvertUserIds(method, ids, convertType, options?.feishuClientId);\n    const mapping = new Map(items.map(item => [item.sourceUserID, item.targetUserID]));\n    return ids.map(id => mapping.get(id) ?? null);\n  }\n\n  /**\n   * 调用批量转换接口（带可观测性）\n   */\n  private async batchConvertUserIds(\n    method: string,\n    userIds: string[],\n    convertType: number,\n    feishuClientId?: string,\n  ): Promise<UserIDMappingItem[]> {\n    const appId = this.requestContext.get('appId');\n    if (!appId) {\n      this.logger.warn(`${method}: no appId in request context`);\n      return [];\n    }\n\n    const url = `/v1/app/${appId}/account/user/convert`;\n    const body = {\n      convertType,\n      userIDList: userIds,\n      ...(feishuClientId ? { feishuClientID: feishuClientId } : {}),\n    };\n\n    const spanName = `${SDK_MODULE}.${method}`;\n    const spanAttrs: Record<string, unknown> = {\n      module: SDK_MODULE,\n      method,\n      source_type: 'platform',\n      paas_attributes_module: SDK_MODULE,\n      http_method: 'POST',\n      path: url,\n    };\n    const logAttrs: Record<string, unknown> = {\n      source_type: 'platform',\n      paas_attributes_module: SDK_MODULE,\n    };\n\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    return this.obs.trace(spanName, async (span: any) => {\n      span.setAttributes(spanAttrs);\n      const elapsed = createTimer();\n\n      try {\n        this.logger.log(`[batchConvertUserIds] request: ${url} ${JSON.stringify(body)}`);\n        const response = await this.httpClient.post(url, body);\n        const logId = response.headers?.get?.('x-tt-logid') ?? '';\n        const raw = await response.text();\n        const durationMs = elapsed();\n\n        this.logger.log(`[batchConvertUserIds] response: status=${response.status} ok=${response.ok} logId=${logId} raw=${raw}`);\n\n        if (!response.ok) {\n          span.setAttributes({ duration_ms: durationMs, success: false, error: `HTTP ${response.status}`, 'x-tt-logid': logId });\n          this.logger.warn(JSON.stringify({\n            method, path: url, duration_ms: durationMs, success: false,\n            error: `HTTP ${response.status} ${response.statusText}`,\n            'x-tt-logid': logId, params: { convertType, count: userIds.length },\n          }), logAttrs);\n          return [];\n        }\n\n        const result: BatchConvertResponse = raw ? JSON.parse(raw) : {};\n\n        if (String(result.status_code) !== '0' && result.status_code !== undefined) {\n          const errMsg = `[${result.status_code}] ${result.status_message || result.error_msg}`;\n          span.setAttributes({ duration_ms: durationMs, success: false, error: errMsg, 'x-tt-logid': logId });\n          this.logger.error(JSON.stringify({\n            method, path: url, duration_ms: durationMs, success: false,\n            error: errMsg, 'x-tt-logid': logId,\n            params: { convertType, count: userIds.length },\n          }), logAttrs);\n          return [];\n        }\n\n        const items = (result.data?.items ?? []).filter(\n          (item): item is UserIDMappingItem => item != null && item.sourceUserID != null,\n        );\n\n        span.setAttributes({ duration_ms: durationMs, success: true, 'x-tt-logid': logId, result_count: items.length });\n        this.logger.log(JSON.stringify({\n          method, path: url, duration_ms: durationMs, success: true,\n          'x-tt-logid': logId,\n          params: { convertType, count: userIds.length },\n          result_count: items.length,\n          response: raw.length > 1024 ? raw.slice(0, 1024) + '...' : raw,\n        }), logAttrs);\n\n        return items;\n      } catch (error: unknown) {\n        const durationMs = elapsed();\n        const errMsg = error instanceof Error ? error.message : String(error);\n\n        const err = error as Record<string, unknown> | undefined;\n        const logId =\n          (err?.headers as { get?: (k: string) => string })?.get?.('x-tt-logid')\n          ?? (err?.response as { headers?: Record<string, string> })?.headers?.['x-tt-logid']\n          ?? err?.logId\n          ?? '';\n        this.logger.error(`[batchConvertUserIds] CATCH: ${errMsg} logId=${logId}`);\n\n        span.setAttributes({ duration_ms: durationMs, success: false, error: errMsg });\n        this.logger.warn(JSON.stringify({\n          method, path: url, duration_ms: durationMs, success: false,\n          error: errMsg,\n          params: { convertType, count: userIds.length },\n        }), logAttrs);\n        return [];\n      }\n    });\n  }\n\n  /**\n   * 按妙搭 userID 批量查用户信息（姓名 + 头像）\n   *\n   * @param userIds - 妙搭用户 ID 列表，最多 100 个\n   * @returns 与入参**等长且同序**的数组；未命中的位置为 `null`\n   *\n   * @throws {Error} 入参超过 100 个，或请求上下文中缺少 appId\n   * @throws {HttpException} 平台调用失败（502），错误信息含平台 code 与 x-tt-logid\n   *\n   * @example\n   * ```ts\n   * const [alice, bob] = await contacts.listUsersByIds([aliceId, bobId]);\n   * const displayName = alice?.name?.zh_cn ?? alice?.name?.en_us ?? '';\n   * const avatarUrl = alice?.avatar?.image?.large;\n   * ```\n   */\n  async listUsersByIds(userIds: string[]): Promise<(MiaodaUserInfo | null)[]> {\n    if (!userIds || userIds.length === 0) {\n      return [];\n    }\n\n    if (userIds.length > MAX_BATCH_SIZE) {\n      throw new Error(`userIds length must not exceed ${MAX_BATCH_SIZE}`);\n    }\n\n    const appId = this.requestContext.get('appId');\n    if (!appId) {\n      // 刻意不静默返回空——OpenAPI 态查不到人和「上下文没配好」是两回事\n      throw new Error('listUsersByIds: no appId in request context');\n    }\n\n    // 去重 + 去空串；请求体只带有效 ID，返回仍按原始入参对齐\n    const queryIds = [...new Set(userIds.filter(id => !!id))];\n    if (queryIds.length === 0) {\n      return userIds.map(() => null);\n    }\n\n    const url = LIST_USERS_PATH(appId);\n    const body = { userIDList: queryIds };\n\n    const items = await this.requestListUsers(url, body);\n    const found = new Map<string, MiaodaUserInfo>(\n      items\n        .filter(item => item != null && item.miaodaUserID != null)\n        .map(item => [String(item.miaodaUserID), pickUserInfo(item)]),\n    );\n\n    // 每个位置给一份独立拷贝：入参含重复 ID 时若共享引用，调用方改一处会连带改\n    // 另一处。必须是结构化拷贝而非 `{ ...hit }`——浅拷贝只隔离顶层，name / avatar\n    // 仍是同一份引用，改 `first.avatar.image.large` 照样连带改 second。\n    return userIds.map(id => {\n      const hit = id ? found.get(id) : undefined;\n      return hit ? structuredClone(hit) : null;\n    });\n  }\n\n  /** 发起平台调用并解析出 items（带可观测性） */\n  private async requestListUsers(\n    url: string,\n    body: { userIDList: string[] },\n  ): Promise<MiaodaUserInfo[]> {\n    const method = 'listUsersByIds';\n    const spanAttrs: Record<string, unknown> = {\n      module: SDK_MODULE,\n      method,\n      source_type: 'platform',\n      paas_attributes_module: SDK_MODULE,\n      http_method: 'POST',\n      path: url,\n    };\n    const logAttrs: Record<string, unknown> = {\n      source_type: 'platform',\n      paas_attributes_module: SDK_MODULE,\n    };\n\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    return this.obs.trace(`${SDK_MODULE}.${method}`, async (span: any) => {\n      span.setAttributes(spanAttrs);\n      const elapsed = createTimer();\n\n      try {\n        const response = await this.httpClient.post(url, body);\n        const logId = response.headers?.get?.('x-tt-logid') ?? '';\n        const raw = await response.text();\n        const items = parseListUsersResponse(response, raw, logId);\n        const durationMs = elapsed();\n\n        span.setAttributes({\n          duration_ms: durationMs,\n          success: true,\n          'x-tt-logid': logId,\n          result_count: items.length,\n        });\n        this.logger.log(\n          JSON.stringify({\n            method,\n            path: url,\n            duration_ms: durationMs,\n            success: true,\n            'x-tt-logid': logId,\n            params: { count: body.userIDList.length },\n            result_count: items.length,\n          }),\n          logAttrs,\n        );\n\n        return items;\n      } catch (error: unknown) {\n        const durationMs = elapsed();\n        const errMsg = error instanceof Error ? error.message : String(error);\n        const logId =\n          error instanceof HttpException\n            ? ((error.getResponse() as Record<string, unknown>)?.['x-tt-logid'] as string) ?? ''\n            : '';\n\n        span.setAttributes({\n          duration_ms: durationMs,\n          success: false,\n          error: errMsg,\n          ...(logId ? { 'x-tt-logid': logId } : {}),\n        });\n        this.logger.warn(\n          JSON.stringify({\n            method,\n            path: url,\n            duration_ms: durationMs,\n            success: false,\n            error: errMsg,\n            'x-tt-logid': logId,\n            params: { count: body.userIDList.length },\n          }),\n          logAttrs,\n        );\n\n        throw error;\n      }\n    });\n  }\n}\n\n/**\n * 解析平台响应。\n *\n * 三个判据并联，任一命中即视为失败：\n * 1. HTTP 非 2xx —— 传输 / 网关层\n * 2. `status_code` 非 '0' —— HTTP InnerAPI 业务层\n * 3. `BaseResp.KStatusCode` 非空 —— RPC 层。**关键**：平台在下游异常时\n *    仍会返回 `StatusCode: 0`，只判 status_code 会把 `tenant not found`\n *    静默降级成「查无此人」。\n *\n * 判据 2/3 与 items 一样，顶层和 `data` 下都查——网关是否包一层未定，\n * 只查一处会让被包裹的错误漏网，等于把要消灭的失效模式又放进来一遍。\n */\nfunction parseListUsersResponse(\n  response: { ok: boolean; status: number },\n  raw: string,\n  logId: string,\n): MiaodaUserInfo[] {\n  if (!response.ok) {\n    let err: ListUsersResponse | undefined;\n    try {\n      err = JSON.parse(raw);\n    } catch {\n      /* 错误体不是 JSON 时忽略，用兜底文案 */\n    }\n    const message = pickPlatformMessage(err, `平台 API 请求失败 (${response.status})`);\n    const httpStatus =\n      response.status >= 500 ? HttpStatus.BAD_GATEWAY : (response.status as number);\n    throwPlatformError(message, httpStatus, err?.status_code, logId);\n  }\n\n  if (!raw) {\n    return [];\n  }\n\n  let json: ListUsersResponse;\n  try {\n    json = JSON.parse(raw);\n  } catch {\n    throwPlatformError('平台 API 响应不是合法 JSON', HttpStatus.BAD_GATEWAY, undefined, logId);\n  }\n\n  // body 是字面量 \"null\" 时 JSON.parse 得到 null，后续取字段会崩\n  if (json == null) {\n    return [];\n  }\n\n  // 错误标记与 items 一样，顶层和 data 下都要查——只查一处会让被包裹的\n  // 下游异常漏网，正是本服务要消灭的那种「静默降级成查无此人」\n  const statusCode = json.status_code ?? json.data?.status_code;\n  if (statusCode != null && String(statusCode) !== '0') {\n    throwPlatformError(\n      pickPlatformMessage(json, `平台业务错误 (${statusCode})`),\n      HttpStatus.BAD_GATEWAY,\n      statusCode,\n      logId,\n    );\n  }\n\n  const baseResp = json.BaseResp ?? json.data?.BaseResp;\n  const kStatusCode = baseResp?.KStatusCode;\n  if (kStatusCode) {\n    throwPlatformError(\n      baseResp?.KStatusMessage || baseResp?.StatusMessage || `平台业务错误 (${kStatusCode})`,\n      HttpStatus.BAD_GATEWAY,\n      kStatusCode,\n      logId,\n    );\n  }\n\n  // 跨租户 / 全部未命中 / 空入参时平台不返回 items 键（不是空数组）\n  return json.data?.items ?? json.items ?? [];\n}\n","import { Controller, Get } from '@nestjs/common';\nimport { ApiExcludeController } from '@nestjs/swagger';\nimport { AuthNPaasService } from '../services/authnpaas.service';\n\n@ApiExcludeController()\n@Controller('api/authnpaas')\nexport class AuthNPaasController {\n  constructor(private readonly authnService: AuthNPaasService) {}\n\n  /**\n   * 获取当前登录用户的飞书 user_id\n   *\n   * GET /api/authnpaas/lark-user-id\n   */\n  @Get('lark-user-id')\n  async getCurrentUserLarkUserId() {\n    const larkUserId = await this.authnService.getCurrentUserLarkUserId();\n    return { lark_user_id: larkUserId };\n  }\n}\n","import { SetMetadata } from '@nestjs/common';\n\n/**\n * Public 装饰器的元数据键\n */\nexport const IS_PUBLIC_KEY = 'isPublic';\n\n/**\n * 标记接口为公开接口，跳过 AuthNPaasGuard 的鉴权检查\n * \n * @example\n * ```typescript\n * @Controller('mock-api/users')\n * @Public()  // 控制器级别\n * export class MockPermissionController {\n *   @Get(':userId/permissions')\n *   getUserPermissions() {}\n * }\n * \n * // 或者在方法级别\n * @Controller('api')\n * export class ApiController {\n *   @Get('public-info')\n *   @Public()  // 方法级别\n *   getPublicInfo() {}\n * }\n * ```\n */\nexport const Public = () => SetMetadata(IS_PUBLIC_KEY, true);\n\n","import { SetMetadata } from '@nestjs/common';\nimport { NEED_LOGIN_KEY } from '../const';\n\n/**\n * NeedLogin 装饰器\n * 标记接口需要登录。如果鉴权失败（如 401/403），异常过滤器会将请求重定向到登录页。\n * 可选地传入登录页路径，默认 '/login'。\n */\nexport const NeedLogin = (loginPath?: string): MethodDecorator & ClassDecorator => {\n  return SetMetadata(NEED_LOGIN_KEY, { loginPath });\n};\n\n\n"],"mappings":"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","names":["import_common","import_core","AUTHNPAAS_MODULE_OPTIONS","Symbol","NEED_LOGIN_KEY","AuthNPaasGuard","reflector","canActivate","context","http","switchToHttp","request","getRequest","response","getResponse","userId","loginUrl","userContext","needLoginMeta","getAllAndOverride","NEED_LOGIN_KEY","getHandler","getClass","setHeader","UnauthorizedException","import_common","SDK_MODULE","LIST_USERS_PATH","appId","MAX_BATCH_SIZE","throwPlatformError","message","httpStatus","platformCode","logId","HttpException","statusCode","code","pickPlatformMessage","body","fallback","error_msg","err_msg","data","pickUserInfo","raw","miaodaUserID","String","name","avatar","CONVERT_TYPE_FORCE_TO_FEISHU","CONVERT_TYPE_FEISHU_TO_FORCE","CONVERT_TYPE_FORCE_TO_LARK_ID","CONVERT_TYPE_LARK_ID_TO_FORCE","CONVERT_TYPE_FORCE_TO_OPEN_ID","CONVERT_TYPE_FORCE_TO_UNION_ID","CONVERT_TYPE_OPEN_ID_TO_FORCE","CONVERT_TYPE_UNION_ID_TO_FORCE","AuthNPaasService","logger","Logger","httpClient","obs","requestContext","getCurrentUserLarkUserId","userId","get","warn","items","batchConvertUserIds","length","targetUserID","getBatchLarkUserIds","userIds","convertInOrder","getBatchMiaodaUserIds","employeeIds","getBatchLarkIds","getBatchMiaodaUserIdsByLarkIds","larkIds","getBatchOpenIds","options","getBatchUnionIds","getBatchMiaodaUserIdsByOpenIds","openIds","getBatchMiaodaUserIdsByUnionIds","unionIds","method","paramName","ids","convertType","Error","feishuClientId","mapping","Map","map","item","sourceUserID","id","url","userIDList","feishuClientID","spanName","spanAttrs","module","source_type","paas_attributes_module","http_method","path","logAttrs","trace","span","setAttributes","elapsed","createTimer","log","JSON","stringify","response","post","headers","text","durationMs","status","ok","duration_ms","success","error","statusText","params","count","result","parse","status_code","undefined","errMsg","status_message","filter","result_count","slice","err","listUsersByIds","queryIds","Set","requestListUsers","found","hit","structuredClone","parseListUsersResponse","getResponse","HttpStatus","BAD_GATEWAY","json","baseResp","BaseResp","kStatusCode","KStatusCode","KStatusMessage","StatusMessage","import_common","AuthNPaasController","authnService","getCurrentUserLarkUserId","larkUserId","lark_user_id","AuthNPaasModule","forRoot","options","module","global","controllers","AuthNPaasController","providers","provide","AUTHNPAAS_MODULE_OPTIONS","useValue","Reflector","AuthNPaasGuard","AuthNPaasService","APP_GUARD","useClass","exports","import_common","IS_PUBLIC_KEY","Public","SetMetadata","import_common","NeedLogin","loginPath","SetMetadata","NEED_LOGIN_KEY"]}