跳到主要内容

高级功能

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

前置条件

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

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

🔄 多项目支持

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

配置注册方式(推荐)

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();

多项目管理器

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. 配置注册优化

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

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

2. 请求缓存系统

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}:${method}:${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: ${modelName}.${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. 批量请求优化

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. 请求去重

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

private generateKey(modelName: string, method: string, params: any): string {
return `${modelName}:${method}:${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" }),
]);
// 实际只会发起一次请求,三个结果是相同的

🛡️ 高级错误处理

错误重试机制

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(
`请求失败,${delay}ms 后重试 (${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" });

断路器模式

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);

🔧 自定义扩展

中间件系统

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(`🚀 ${context.modelName}.${context.method}`, context.params);
return context;
},
after: async (context) => {
console.log(
`${context.modelName}.${context.method} completed in ${context.duration}ms`
);
return context;
},
error: async (context) => {
console.error(
`${context.modelName}.${context.method} failed after ${context.duration}ms:`,
context.error.message
);
},
};

const metricsMiddleware: Middleware = {
name: "metrics",
after: async (context) => {
// 发送指标到监控系统
sendMetric(`lovrabet.api.${context.modelName}.${context.method}`, {
duration: context.duration,
success: true,
});
return context;
},
error: async (context) => {
sendMetric(`lovrabet.api.${context.modelName}.${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,无需预先配置
  • 零维护:新增数据集无需修改代码

核心实现

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 服务端示例

// 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 });
},
});

前端调用示例

// 前端代码:调用代理 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 示例

// 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 示例

// 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 只是本地标识符

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

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

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

2. 为什么不需要替换 _proxy

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

// 类似于给变量起名
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

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

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

安全注意事项

  1. 永远不要在前端暴露 accessKey

    // ❌ 错误:前端直接使用 accessKey
    const client = createClient({
    accessKey: "your-access-key", // 暴露在客户端代码中!
    });

    // ✅ 正确:通过服务端代理
    fetch("/api/openapi-proxy/getList", {
    body: JSON.stringify({ datasetCode: "xxx" }),
    });
  2. 添加身份验证

    // 验证用户身份
    const session = await getSession(request);
    if (!session) {
    return Response.json({ error: "Unauthorized" }, { status: 401 });
    }
  3. datasetCode 白名单(可选)

    // 如果需要限制可访问的数据集
    const allowedDatasets = ["dataset-001", "dataset-002"];
    if (!allowedDatasets.includes(datasetCode)) {
    return Response.json({ error: "Dataset not allowed" }, { status: 403 });
    }

优势总结

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

这种模式特别适合:

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

📖 下一步

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

❓ 常见问题

Q: 如何监控 SDK 的性能?

// 性能监控示例
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: 如何实现请求的优先级队列?

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(); // 处理下一个
}
}
}