高级功能
本文介绍 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 });
安全注意事项
-
永远不要在前端暴露 accessKey
// ❌ 错误:前端直接使用 accessKey
const client = createClient({
accessKey: "your-access-key", // 暴露在客户端代码中!
});
// ✅ 正确:通过服务端代理
fetch("/api/openapi-proxy/getList", {
body: JSON.stringify({ datasetCode: "xxx" }),
}); -
添加身份验证
// 验证用户身份
const session = await getSession(request);
if (!session) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
} -
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(); // 处理下一个
}
}
}