Skip to content

高级功能

本文介绍 Lovrabet SDK 的高级功能,包括多项目支持、性能优化、错误处理和扩展功能。

💡 前置条件开始之前,请确保已完成 SDK 配置。推荐使用 CLI 自动生成配置。

以下示例使用别名方式 client.models.users 以便阅读,也可以使用标准方式 client.models.dataset_xxx(功能完全一致)。

🔄 多项目支持

SDK 完美支持在同一应用中访问多个项目的 API。

配置注册方式(推荐)

TypeScript
import { registerModels, createClient } from "@lovrabet/sdk";

// 注册多个项目配置
registerModels(
  {
    appCode: "project-a",
    models: [
      { datasetCode: "dataset-a-001", tableName: "users_a", alias: "users" },
      { datasetCode: "dataset-a-002", tableName: "orders_a", alias: "orders" },
    ],
  },
  "project-a" // 配置名称
);

registerModels(
  {
    appCode: "project-b",
    models: [
      { datasetCode: "dataset-b-001", tableName: "users_b", alias: "users" },
      { datasetCode: "dataset-b-002", tableName: "products_b", alias: "products" },
    ],
  },
  "project-b"
);

// 创建不同项目的客户端
const projectA = createClient("project-a");
const projectB = createClient("project-b");

// 完全隔离,互不影响
const usersFromA = await projectA.models.users.filter();
const usersFromB = await projectB.models.users.filter();

多项目管理器

TypeScript
class MultiProjectManager {
  private clients = new Map<string, any>();
  private configs = new Map<string, any>();

  addProject(name: string, config: any) {
    this.configs.set(name, config);
    registerModels(config, name);
    this.clients.set(name, createClient(name));
  }

  getClient(projectName: string) {
    const client = this.clients.get(projectName);
    if (!client) {
      throw new Error(`Project ${projectName} not found`);
    }
    return client;
  }

  listProjects() {
    return Array.from(this.clients.keys());
  }

  // 跨项目数据聚合
  async aggregateData(projectNames: string[], modelName: string, params?: any) {
    const results = await Promise.all(
      projectNames.map(async (projectName) => {
        const client = this.getClient(projectName);
        const data = await client.models[modelName].filter(params);
        return {
          project: projectName,
          data: data.tableData,
          total: data.total,
        };
      })
    );

    return {
      projects: results,
      totalRecords: results.reduce((sum, result) => sum + result.total, 0),
    };
  }
}

// 使用多项目管理器
const projectManager = new MultiProjectManager();

// 添加项目
projectManager.addProject("ecommerce", {
  appCode: "ecommerce-app",
  models: [
    { datasetCode: "ecommerce-orders", tableName: "orders", alias: "orders" },
    { datasetCode: "ecommerce-customers", tableName: "customers", alias: "customers" },
  ],
});

projectManager.addProject("analytics", {
  appCode: "analytics-app",
  models: [
    { datasetCode: "analytics-events", tableName: "events", alias: "events" },
    { datasetCode: "analytics-reports", tableName: "reports", alias: "reports" },
  ],
});

// 获取特定项目的数据
const ecommerceClient = projectManager.getClient("ecommerce");
const orders = await ecommerceClient.models.orders.filter();

// 跨项目数据聚合
const aggregatedData = await projectManager.aggregateData(
  ["ecommerce", "analytics"],
  "users",
  { status: "active" }
);

⚡ 性能优化

1. 配置注册优化

TypeScript
// ✅ 推荐:预注册配置
registerModels(LARGE_CONFIG);
const client1 = createClient(); // 无参数,性能最佳
const client2 = createClient(); // 复用已注册配置

// ❌ 不推荐:每次都传入配置
const client1 = createClient(LARGE_CONFIG); // 每次都要处理配置
const client2 = createClient(LARGE_CONFIG); // 重复传入

2. 请求缓存系统

TypeScript
interface CacheOptions {
  ttl?: number; // 缓存时间(毫秒)
  maxSize?: number; // 最大缓存项数
  keyGenerator?: (params: any) => string; // 自定义键生成器
}

class RequestCache {
  private cache = new Map<
    string,
    { data: any; timestamp: number; ttl: number }
  >();
  private maxSize: number;

  constructor(private options: CacheOptions = {}) {
    this.maxSize = options.maxSize || 1000;
  }

  private generateKey(modelName: string, method: string, params: any): string {
    if (this.options.keyGenerator) {
      return this.options.keyGenerator({ modelName, method, params });
    }
    return `${modelName}:<equation>{method}:</equation>{JSON.stringify(params)}`;
  }

  get<T>(modelName: string, method: string, params: any): T | null {
    const key = this.generateKey(modelName, method, params);
    const item = this.cache.get(key);

    if (!item) return null;

    // 检查是否过期
    if (Date.now() - item.timestamp > item.ttl) {
      this.cache.delete(key);
      return null;
    }

    return item.data;
  }

  set(modelName: string, method: string, params: any, data: any, ttl?: number) {
    const key = this.generateKey(modelName, method, params);

    // 清理过期项
    if (this.cache.size >= this.maxSize) {
      this.cleanup();
    }

    this.cache.set(key, {
      data,
      timestamp: Date.now(),
      ttl: ttl || this.options.ttl || 5 * 60 * 1000, // 默认5分钟
    });
  }

  private cleanup() {
    const now = Date.now();
    const entries = Array.from(this.cache.entries());

    // 删除过期项
    for (const [key, item] of entries) {
      if (now - item.timestamp > item.ttl) {
        this.cache.delete(key);
      }
    }

    // 如果还是太多,删除最旧的项
    if (this.cache.size >= this.maxSize) {
      const sortedEntries = entries
        .filter(([key]) => this.cache.has(key))
        .sort(([, a], [, b]) => a.timestamp - b.timestamp);

      const toDelete = sortedEntries.slice(0, Math.floor(this.maxSize * 0.2));
      toDelete.forEach(([key]) => this.cache.delete(key));
    }
  }

  clear() {
    this.cache.clear();
  }

  size() {
    return this.cache.size;
  }
}

// 带缓存的客户端包装器
class CachedClient {
  private cache = new RequestCache({ ttl: 10 * 60 * 1000 }); // 10分钟缓存

  constructor(private client: any) {}

  async cachedRequest<T>(
    modelName: string,
    method: string,
    params: any = {},
    options: { useCache?: boolean; cacheTtl?: number } = {}
  ): Promise<T> {
    const { useCache = true, cacheTtl } = options;

    // 尝试从缓存获取
    if (useCache && method === "getList") {
      const cached = this.cache.get<T>(modelName, method, params);
      if (cached) {
        console.log(`Cache hit: <equation>{modelName}.</equation>{method}`);
        return cached;
      }
    }

    // 从 API 获取
    const result = await this.client.models[modelName][method](params);

    // 存入缓存(只缓存查询操作)
    if (useCache && method === "getList") {
      this.cache.set(modelName, method, params, result, cacheTtl);
    }

    // 如果是修改操作,清理相关缓存
    if (["create", "update", "delete"].includes(method)) {
      this.invalidateCache(modelName);
    }

    return result;
  }

  private invalidateCache(modelName: string) {
    // 简单实现:清理所有该模型的缓存
    this.cache.clear(); // 或者实现更精确的缓存失效逻辑
  }

  // 便捷方法
  async getList<T>(modelName: string, params?: any, options?: any): Promise<T> {
    return this.cachedRequest(modelName, "getList", params, options);
  }

  async create<T>(modelName: string, data: any): Promise<T> {
    return this.cachedRequest(modelName, "create", data, { useCache: false });
  }

  async update<T>(modelName: string, id: string, data: any): Promise<T> {
    return this.cachedRequest(
      modelName,
      "update",
      { id, data },
      { useCache: false }
    );
  }
}

// 使用缓存客户端
const cachedClient = new CachedClient(client);

// 第一次请求会从 API 获取
const users1 = await cachedClient.filter("users", { status: "active" });

// 第二次相同请求会从缓存返回
const users2 = await cachedClient.filter("users", { status: "active" });

3. 批量请求优化

TypeScript
class BatchRequestManager {
  private batchQueue = new Map<string, any[]>();
  private batchTimer: NodeJS.Timeout | null = null;
  private batchDelay = 50; // 50ms 内的请求会被批量处理

  constructor(private client: any) {}

  async batchGetList<T>(
    requests: Array<{ modelName: string; params?: any }>
  ): Promise<T[]> {
    // 执行批量请求
    const promises = requests.map(({ modelName, params }) =>
      this.client.models[modelName].filter(params)
    );

    return Promise.all(promises);
  }

  async batchCreate<T>(modelName: string, items: any[]): Promise<T[]> {
    // 分批处理,避免单次请求过大
    const batchSize = 10;
    const results: T[] = [];

    for (let i = 0; i < items.length; i += batchSize) {
      const batch = items.slice(i, i + batchSize);

      const batchPromises = batch.map((item) =>
        this.client.models[modelName].create(item)
      );

      const batchResults = await Promise.all(batchPromises);
      results.push(...batchResults);

      // 避免请求过于频繁
      if (i + batchSize < items.length) {
        await new Promise((resolve) => setTimeout(resolve, 100));
      }
    }

    return results;
  }

  // 智能批量更新
  async smartBatchUpdate<T>(
    modelName: string,
    updates: Array<{ id: string; data: any }>
  ): Promise<T[]> {
    // 按更新数据的相似性分组
    const groups = this.groupSimilarUpdates(updates);

    const results: T[] = [];

    for (const group of groups) {
      const groupPromises = group.map(({ id, data }) =>
        this.client.models[modelName].update(id, data)
      );

      const groupResults = await Promise.all(groupPromises);
      results.push(...groupResults);

      // 组间延迟
      await new Promise((resolve) => setTimeout(resolve, 50));
    }

    return results;
  }

  private groupSimilarUpdates(
    updates: Array<{ id: string; data: any }>
  ): Array<Array<{ id: string; data: any }>> {
    // 简单实现:按更新字段分组
    const groups = new Map<string, Array<{ id: string; data: any }>>();

    updates.forEach((update) => {
      const key = Object.keys(update.data).sort().join(",");
      if (!groups.has(key)) {
        groups.set(key, []);
      }
      groups.get(key)!.push(update);
    });

    return Array.from(groups.values());
  }
}

// 使用批量管理器
const batchManager = new BatchRequestManager(client);

// 批量获取不同模型的数据
const [users, orders, products] = await batchManager.batchGetList([
  { modelName: "users", params: { status: "active" } },
  { modelName: "orders", params: { status: "pending" } },
  { modelName: "products", params: { inStock: true } },
]);

// 批量创建
const newUsers = await batchManager.batchCreate("users", [
  { name: "User 1", email: "user1@example.com" },
  { name: "User 2", email: "user2@example.com" },
  // ... 更多用户
]);

4. 请求去重

TypeScript
class RequestDeduplicator {
  private pendingRequests = new Map<string, Promise<any>>();

  private generateKey(modelName: string, method: string, params: any): string {
    return `${modelName}:<equation>{method}:</equation>{JSON.stringify(params)}`;
  }

  async request<T>(
    modelName: string,
    method: string,
    params?: any
  ): Promise<T> {
    const key = this.generateKey(modelName, method, params);

    // 如果相同的请求正在进行中,直接返回该 Promise
    if (this.pendingRequests.has(key)) {
      console.log(`Request deduplication: ${key}`);
      return this.pendingRequests.get(key);
    }

    // 创建新的请求
    const promise = client.models[modelName][method](params).finally(() => {
      // 请求完成后清除缓存
      this.pendingRequests.delete(key);
    });

    this.pendingRequests.set(key, promise);
    return promise;
  }
}

const deduplicator = new RequestDeduplicator();

// 多次调用相同参数的请求会被合并
const [result1, result2, result3] = await Promise.all([
  deduplicator.request("users", "getList", { status: "active" }),
  deduplicator.request("users", "getList", { status: "active" }),
  deduplicator.request("users", "getList", { status: "active" }),
]);
// 实际只会发起一次请求,三个结果是相同的

🛡️ 高级错误处理

错误重试机制

TypeScript
interface RetryOptions {
  maxRetries: number;
  baseDelay: number;
  maxDelay: number;
  backoffFactor: number;
  retryCondition?: (error: any) => boolean;
}

class RetryableClient {
  constructor(private client: any, private retryOptions: RetryOptions) {}

  async withRetry<T>(operation: () => Promise<T>): Promise<T> {
    let lastError: any;
    let delay = this.retryOptions.baseDelay;

    for (
      let attempt = 1;
      attempt <= this.retryOptions.maxRetries + 1;
      attempt++
    ) {
      try {
        return await operation();
      } catch (error: any) {
        lastError = error;

        // 检查是否应该重试
        if (
          attempt > this.retryOptions.maxRetries ||
          !this.shouldRetry(error)
        ) {
          break;
        }

        console.log(
          `请求失败,<equation>{delay}ms 后重试 (</equation>{attempt}/${this.retryOptions.maxRetries})`
        );

        // 等待后重试
        await new Promise((resolve) => setTimeout(resolve, delay));

        // 指数退避
        delay = Math.min(
          delay * this.retryOptions.backoffFactor,
          this.retryOptions.maxDelay
        );
      }
    }

    throw lastError;
  }

  private shouldRetry(error: any): boolean {
    // 自定义重试条件
    if (this.retryOptions.retryCondition) {
      return this.retryOptions.retryCondition(error);
    }

    // 默认重试条件
    const retryableStatuses = [408, 429, 502, 503, 504];
    return (
      retryableStatuses.includes(error.status) || error.code === "NETWORK_ERROR"
    );
  }

  // 包装原始方法
  async getList<T>(modelName: string, params?: any): Promise<T> {
    return this.withRetry(() => this.client.models[modelName].filter(params));
  }

  async create<T>(modelName: string, data: any): Promise<T> {
    return this.withRetry(() => this.client.models[modelName].create(data));
  }
}

// 使用重试客户端
const retryableClient = new RetryableClient(client, {
  maxRetries: 3,
  baseDelay: 1000,
  maxDelay: 10000,
  backoffFactor: 2,
  retryCondition: (error) => error.status >= 500 || error.status === 429,
});

// 自动重试的请求
const users = await retryableClient.filter("users", { status: "active" });

断路器模式

TypeScript
interface CircuitBreakerOptions {
  failureThreshold: number; // 失败次数阈值
  recoveryTimeout: number; // 恢复尝试超时
  monitoringPeriod: number; // 监控周期
}

enum CircuitState {
  CLOSED = "CLOSED", // 正常状态
  OPEN = "OPEN", // 断开状态
  HALF_OPEN = "HALF_OPEN", // 半开状态
}

class CircuitBreaker {
  private state = CircuitState.CLOSED;
  private failures = 0;
  private lastFailureTime = 0;
  private nextAttempt = 0;

  constructor(private options: CircuitBreakerOptions) {}

  async execute<T>(operation: () => Promise<T>): Promise<T> {
    if (this.state === CircuitState.OPEN) {
      if (Date.now() < this.nextAttempt) {
        throw new Error("Circuit breaker is OPEN");
      }
      this.state = CircuitState.HALF_OPEN;
    }

    try {
      const result = await operation();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess() {
    this.failures = 0;
    this.state = CircuitState.CLOSED;
  }

  private onFailure() {
    this.failures++;
    this.lastFailureTime = Date.now();

    if (this.failures >= this.options.failureThreshold) {
      this.state = CircuitState.OPEN;
      this.nextAttempt = Date.now() + this.options.recoveryTimeout;
    }
  }

  getState(): CircuitState {
    return this.state;
  }

  getMetrics() {
    return {
      state: this.state,
      failures: this.failures,
      lastFailureTime: this.lastFailureTime,
      nextAttempt: this.nextAttempt,
    };
  }
}

// 为每个模型创建断路器
class CircuitBreakerClient {
  private breakers = new Map<string, CircuitBreaker>();

  constructor(private client: any) {}

  private getBreaker(modelName: string): CircuitBreaker {
    if (!this.breakers.has(modelName)) {
      this.breakers.set(
        modelName,
        new CircuitBreaker({
          failureThreshold: 5,
          recoveryTimeout: 60000,
          monitoringPeriod: 10000,
        })
      );
    }
    return this.breakers.get(modelName)!;
  }

  async getList<T>(modelName: string, params?: any): Promise<T> {
    const breaker = this.getBreaker(modelName);
    return breaker.execute(() => this.client.models[modelName].filter(params));
  }

  getHealthStatus() {
    const status = {};
    this.breakers.forEach((breaker, modelName) => {
      status[modelName] = breaker.getMetrics();
    });
    return status;
  }
}

const breakerClient = new CircuitBreakerClient(client);

// 监控健康状态
setInterval(() => {
  console.log("Circuit Breaker Status:", breakerClient.getHealthStatus());
}, 30000);

🔧 自定义扩展

中间件系统

TypeScript
interface Middleware {
  name: string;
  before?: (context: RequestContext) => Promise<RequestContext>;
  after?: (context: ResponseContext) => Promise<ResponseContext>;
  error?: (context: ErrorContext) => Promise<void>;
}

interface RequestContext {
  modelName: string;
  method: string;
  params: any;
  metadata: Record<string, any>;
}

interface ResponseContext extends RequestContext {
  response: any;
  duration: number;
}

interface ErrorContext extends RequestContext {
  error: any;
  duration: number;
}

class MiddlewareClient {
  private middlewares: Middleware[] = [];

  constructor(private client: any) {}

  use(middleware: Middleware) {
    this.middlewares.push(middleware);
    return this;
  }

  async execute<T>(
    modelName: string,
    method: string,
    params?: any
  ): Promise<T> {
    let context: RequestContext = {
      modelName,
      method,
      params,
      metadata: {},
    };

    const startTime = Date.now();

    try {
      // 执行 before 中间件
      for (const middleware of this.middlewares) {
        if (middleware.before) {
          context = await middleware.before(context);
        }
      }

      // 执行实际请求
      const response = await this.client.models[modelName][method](
        context.params
      );
      const duration = Date.now() - startTime;

      let responseContext: ResponseContext = {
        ...context,
        response,
        duration,
      };

      // 执行 after 中间件
      for (const middleware of this.middlewares.reverse()) {
        if (middleware.after) {
          responseContext = await middleware.after(responseContext);
        }
      }

      return responseContext.response;
    } catch (error) {
      const duration = Date.now() - startTime;
      const errorContext: ErrorContext = {
        ...context,
        error,
        duration,
      };

      // 执行 error 中间件
      for (const middleware of this.middlewares) {
        if (middleware.error) {
          await middleware.error(errorContext);
        }
      }

      throw error;
    }
  }
}

// 内置中间件
const loggingMiddleware: Middleware = {
  name: "logging",
  before: async (context) => {
    console.log(`🚀 <equation>{context.modelName}.</equation>{context.method}`, context.params);
    return context;
  },
  after: async (context) => {
    console.log(
      `✅ <equation>{context.modelName}.</equation>{context.method} completed in ${context.duration}ms`
    );
    return context;
  },
  error: async (context) => {
    console.error(
      `❌ <equation>{context.modelName}.</equation>{context.method} failed after ${context.duration}ms:`,
      context.error.message
    );
  },
};

const metricsMiddleware: Middleware = {
  name: "metrics",
  after: async (context) => {
    // 发送指标到监控系统
    sendMetric(`lovrabet.api.<equation>{context.modelName}.</equation>{context.method}`, {
      duration: context.duration,
      success: true,
    });
    return context;
  },
  error: async (context) => {
    sendMetric(`lovrabet.api.<equation>{context.modelName}.</equation>{context.method}`, {
      duration: context.duration,
      success: false,
      error: context.error.message,
    });
  },
};

// 使用中间件
const middlewareClient = new MiddlewareClient(client)
  .use(loggingMiddleware)
  .use(metricsMiddleware);

// 现在所有请求都会经过中间件处理
const users = await middlewareClient.execute("users", "getList", {
  status: "active",
});

🔐 服务端代理模式(动态 datasetCode)

在服务端场景中,你可能需要创建一个代理 API,接受前端传来的 datasetCode,然后代理调用 Lovrabet OpenAPI。这种模式有以下优点:

  • 安全性accessKey 只在服务端配置,不暴露给前端
  • 灵活性:支持任意 datasetCode,无需预先配置
  • 零维护:新增数据集无需修改代码

核心实现

TypeScript
import { createClient } from "@lovrabet/sdk";

/**
 * 动态创建客户端代理请求
 * @param datasetCode 前端传入的数据集 ID
 * @param operation 操作名称(getList/getOne/create/update)
 * @param params 操作参数
 */
async function proxyRequest(
  datasetCode: string,
  operation: string,
  params?: any
) {
  // 动态创建客户端,使用前端传入的 datasetCode
  const client = createClient({
    appCode: process.env.LOVRABET_APP_CODE, // 服务端配置
    accessKey: process.env.LOVRABET_ACCESS_KEY, // 服务端配置(安全)
    env: "online",
    models: {
      _proxy: {
        // 本地变量名(任意命名)
        tableName: "_proxy", // 仅用于标识(OpenAPI 不使用)
        datasetCode: datasetCode, // 前端传入的真实数据集 ID
      },
    },
  });

  // 执行操作
  const result = await client.models._proxy[operation](params);
  return result;
}

Node.js / Bun 服务端示例

TypeScript
// server.ts
import { createClient } from "@lovrabet/sdk";

// 路由处理器:POST /api/openapi-proxy/:operation
async function handleProxyRequest(request: Request) {
  const url = new URL(request.url);
  const operation = url.pathname.split("/").pop(); // 提取操作名

  // 解析请求体
  const { datasetCode, params } = await request.json();

  // 参数验证
  if (!datasetCode) {
    return Response.json({ error: "datasetCode is required" }, { status: 400 });
  }

  // 支持的操作
  const supportedOps = [
    "getList",
    "getOne",
    "create",
    "update",
    "getDatasetList",
  ];
  if (!supportedOps.includes(operation)) {
    return Response.json(
      { error: `Operation "${operation}" not supported` },
      { status: 400 }
    );
  }

  try {
    // 动态创建客户端
    const client = createClient({
      appCode: process.env.LOVRABET_APP_CODE!,
      accessKey: process.env.LOVRABET_ACCESS_KEY!,
      env: "online",
      models: {
        _proxy: {
          tableName: "_proxy",
          datasetCode: datasetCode, // 使用前端传入的 datasetCode
        },
      },
    });

    // 执行操作
    const result = await client.models._proxy[operation](params);

    return Response.json({
      success: true,
      data: result,
      operation,
      datasetCode,
    });
  } catch (error: any) {
    console.error("Proxy request failed:", error);
    return Response.json(
      {
        success: false,
        error: error.message,
      },
      { status: 500 }
    );
  }
}

// Bun.serve 示例
Bun.serve({
  port: 3000,
  async fetch(request) {
    const url = new URL(request.url);

    // 匹配 /api/openapi-proxy/* 路由
    if (url.pathname.startsWith("/api/openapi-proxy/")) {
      return handleProxyRequest(request);
    }

    return new Response("Not Found", { status: 404 });
  },
});

前端调用示例

TypeScript
// 前端代码:调用代理 API
async function fetchUsers(datasetCode: string, page = 1, pageSize = 20) {
  const response = await fetch("/api/openapi-proxy/getList", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      datasetCode: datasetCode, // 动态传入任意 datasetCode
      params: {
        currentPage: page,
        pageSize: pageSize,
      },
    }),
  });

  const result = await response.json();
  return result.data;
}

// 使用不同的 datasetCode
const users = await fetchUsers("dataset-001");
const orders = await fetchUsers("dataset-002");
const products = await fetchUsers("dataset-003");

Next.js API Route 示例

TypeScript
// app/api/openapi-proxy/[operation]/route.ts
import { createClient } from "@lovrabet/sdk";

export async function POST(
  request: Request,
  { params }: { params: { operation: string } }
) {
  const { operation } = params;
  const { datasetCode, params: requestParams } = await request.json();

  // 参数验证
  if (!datasetCode) {
    return Response.json({ error: "datasetCode is required" }, { status: 400 });
  }

  // 动态创建客户端
  const client = createClient({
    appCode: process.env.LOVRABET_APP_CODE!,
    accessKey: process.env.LOVRABET_ACCESS_KEY!,
    models: {
      _proxy: { tableName: "_proxy", datasetCode },
    },
  });

  try {
    const result = await client.models._proxy[operation](requestParams);
    return Response.json({ success: true, data: result });
  } catch (error: any) {
    return Response.json({ error: error.message }, { status: 500 });
  }
}

Express 示例

TypeScript
// express-server.ts
import express from "express";
import { createClient } from "@lovrabet/sdk";

const app = express();
app.use(express.json());

// 代理路由
app.post("/api/openapi-proxy/:operation", async (req, res) => {
  const { operation } = req.params;
  const { datasetCode, params } = req.body;

  if (!datasetCode) {
    return res.status(400).json({ error: "datasetCode is required" });
  }

  try {
    const client = createClient({
      appCode: process.env.LOVRABET_APP_CODE!,
      accessKey: process.env.LOVRABET_ACCESS_KEY!,
      models: {
        _proxy: { tableName: "_proxy", datasetCode },
      },
    });

    const result = await client.models._proxy[operation](params);
    res.json({ success: true, data: result });
  } catch (error: any) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000, () => {
  console.log("Proxy server running on port 3000");
});

关键点说明

1. _proxy 只是本地标识符

TypeScript
models: {
  _proxy: {              // ← 可以改成任意名字(foo/bar/temp)
    tableName: "_proxy", // ← OpenAPI 不使用这个值
    datasetCode: "xxx",  // ← 这才是真正发送给服务器的值
  },
}

发送给 Lovrabet 的请求中,只有 datasetCode 会被使用:

JSON
POST /openapi/data/get-list
{
  "appCode": "app-c4055413",
  "datasetCode": "xxx",  // ← 使用这个值
  "paramMap": { ... }
}

2. 为什么不需要替换 _proxy

_proxy 只是用来访问模型实例的"别名",SDK 内部会自动提取 datasetCode 的值。类比:

TypeScript
// 类似于给变量起名
const user = { id: 123 };
const person = { id: 123 };

// 变量名不同,但发送的数据相同
fetch("/api", { body: JSON.stringify(user) }); // 发送 { id: 123 }
fetch("/api", { body: JSON.stringify(person) }); // 发送 { id: 123 }

3. 支持任意 datasetCode

这种模式的最大优点是零配置

TypeScript
// 无需预先注册,支持任意 datasetCode
proxyRequest("dataset-001", "getList", { currentPage: 1 });
proxyRequest("dataset-002", "getList", { currentPage: 1 });
proxyRequest("brand-new-dataset", "getList", { currentPage: 1 });

安全注意事项

  1. 永远不要在前端暴露 accessKey
  2. 添加身份验证
  3. datasetCode 白名单(可选)

优势总结

特性传统方式动态代理模式
安全性需在前端配置 accessKey✅ accessKey 仅在服务端
灵活性需预先注册所有模型✅ 支持任意 datasetCode
维护成本新增数据集需修改代码✅ 零配置,无需修改
适用场景数据集固定的应用✅ 多租户、SaaS 平台

这种模式特别适合:

  • 🏢 多租户应用:每个租户有独立的数据集
  • 🔧 后台管理系统:需要访问多个动态数据集
  • 🚀 SaaS 平台:客户可自定义数据集

📖 下一步

了解高级功能后,您可以继续学习:

  • 🛠️ 实际应用示例 - React/Vue 集成示例
  • 🔧 故障排查 - 常见问题和调试技巧

❓ 常见问题

Q: 如何监控 SDK 的性能?

TypeScript
// 性能监控示例
class PerformanceMonitor {
  private metrics = new Map<
    string,
    Array<{ duration: number; timestamp: number }>
  >();

  recordRequest(key: string, duration: number) {
    if (!this.metrics.has(key)) {
      this.metrics.set(key, []);
    }

    const records = this.metrics.get(key)!;
    records.push({ duration, timestamp: Date.now() });

    // 只保留最近100条记录
    if (records.length > 100) {
      records.shift();
    }
  }

  getStats(key: string) {
    const records = this.metrics.get(key) || [];
    if (records.length === 0) return null;

    const durations = records.map((r) => r.duration);
    return {
      count: records.length,
      avg: durations.reduce((a, b) => a + b, 0) / durations.length,
      min: Math.min(...durations),
      max: Math.max(...durations),
      p95: this.percentile(durations, 0.95),
    };
  }

  private percentile(arr: number[], p: number) {
    const sorted = arr.slice().sort((a, b) => a - b);
    const index = Math.ceil(sorted.length * p) - 1;
    return sorted[index];
  }
}

Q: 如何实现请求的优先级队列?

TypeScript
enum RequestPriority {
  HIGH = 1,
  MEDIUM = 2,
  LOW = 3,
}

class PriorityQueue {
  private queue: Array<{
    request: () => Promise<any>;
    priority: RequestPriority;
    resolve: any;
    reject: any;
  }> = [];
  private processing = false;
  private maxConcurrent = 3;
  private currentlyProcessing = 0;

  async enqueue<T>(
    request: () => Promise<T>,
    priority: RequestPriority = RequestPriority.MEDIUM
  ): Promise<T> {
    return new Promise((resolve, reject) => {
      this.queue.push({ request, priority, resolve, reject });
      this.queue.sort((a, b) => a.priority - b.priority);
      this.process();
    });
  }

  private async process() {
    if (this.processing || this.currentlyProcessing >= this.maxConcurrent)
      return;

    const item = this.queue.shift();
    if (!item) return;

    this.currentlyProcessing++;

    try {
      const result = await item.request();
      item.resolve(result);
    } catch (error) {
      item.reject(error);
    } finally {
      this.currentlyProcessing--;
      this.process(); // 处理下一个
    }
  }
}

基于飞书知识库同步生成,内容以飞书源文档为准