Skip to content

快速开始

本指南将帮助您在 5 分钟内完成第一个 OpenAPI 调用。我们推荐使用官方 SDK,它会自动处理认证、签名等复杂逻辑。

TIP

推荐使用 SDK官方 SDK 已经封装了所有底层实现,开箱即用。如果您需要了解底层实现,请查看本文档末尾的"底层实现"章节。

前置要求

开始之前,请确保:

  • ✅ 已获得 OpenAPI 使用权限(联系商务经理)

  • ✅ 已获得以下凭证:

    • App Code - 应用编码
    • Access Key - 访问密钥
    • Dataset Code - 数据集编码
  • ✅ 已安装 Node.js 16+ 或 Bun

安装 SDK

Bash
npm install @lovrabet/sdk
# 或
bun add @lovrabet/sdk

场景 1: 服务端使用(Node.js/SSR)

适用于 Node.js 服务端、Next.js SSR、API 路由等场景。

基础用法

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

// 创建客户端(使用 accessKey)
const client = createClient({
  appCode: "your-app-code",
  accessKey: process.env.LOVRABET_ACCESS_KEY, // 从环境变量读取
  models: {
    users: {
      tableName: "users",
      datasetCode: "your-dataset-code",
    },
  },
});

// 查询数据列表
const response = await client.models.users.filter({
  currentPage: 1,
  pageSize: 20,
});

console.log("总条数:", response.paging.totalCount);
console.log("数据:", response.tableData);

// 查询单条数据
const user = await client.models.users.getOne("user-id");
console.log("用户详情:", user);

Next.js App Router 示例

在 Next.js 服务端组件中使用:

TypeScript
// app/users/page.tsx (Server Component)
import { createClient } from "@lovrabet/sdk";

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

export default async function UsersPage() {
  // 直接在服务端组件中调用
  const { tableData: users } = await client.models.users.filter({
    pageSize: 10,
    currentPage: 1,
  });

  return (
    <div>
      <h1>用户列表</h1>
      {users.map((user) => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

Next.js API 路由示例

创建 API 路由供前端调用:

TypeScript
// app/api/users/route.ts
import { createClient } from "@lovrabet/sdk";
import { NextResponse } from "next/server";

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

export async function GET(request: Request) {
  try {
    const { searchParams } = new URL(request.url);
    const page = parseInt(searchParams.get("page") || "1");
    const size = parseInt(searchParams.get("size") || "20");

    const data = await client.models.users.filter({
      currentPage: page,
      pageSize: size,
    });

    return NextResponse.json(data);
  } catch (error) {
    return NextResponse.json({ error: "查询失败" }, { status: 500 });
  }
}

场景 2: 浏览器端使用(预生成 Token)

适用于浏览器端公开数据访问、未登录用户等场景。

步骤 1: 服务端生成 Token

创建 API 路由生成 Token:

TypeScript
// app/api/token/route.ts
import { generateOpenApiToken } from "@lovrabet/sdk";
import { NextResponse } from "next/server";

export async function GET() {
  try {
    const result = await generateOpenApiToken({
      appCode: process.env.LOVRABET_APP_CODE!,
      datasetCode: process.env.LOVRABET_DATASET_CODE!,
      accessKey: process.env.LOVRABET_ACCESS_KEY!,
    });

    // 返回 token、timestamp 和 expiresAt
    return NextResponse.json(result);
  } catch (error) {
    return NextResponse.json({ error: "Token 生成失败" }, { status: 500 });
  }
}

步骤 2: 浏览器使用 Token

TypeScript
// app/users/client-page.tsx
"use client";

import { createClient } from "@lovrabet/sdk";
import { useEffect, useState } from "react";

export default function ClientUsersPage() {
  const [users, setUsers] = useState([]);
  const [client, setClient] = useState(null);

  useEffect(() => {
    async function init() {
      // 1. 获取 token
      const { token, timestamp } = await fetch("/api/token").then((r) =>
        r.json()
      );

      // 2. 创建客户端
      const newClient = createClient({
        appCode: "your-app-code",
        token: token,
        timestamp: timestamp,
        models: {
          users: {
            tableName: "users",
            datasetCode: "your-dataset-code",
          },
        },
      });

      setClient(newClient);

      // 3. 查询数据
      const { tableData } = await newClient.models.users.filter({
        pageSize: 10,
      });
      setUsers(tableData);
    }

    init();
  }, []);

  return (
    <div>
      <h1>用户列表</h1>
      {users.map((user) => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

Token 自动刷新

Token 有效期为 10 分钟,需要定期刷新:

TypeScript
"use client";

import { createClient, isTokenExpiring } from "@lovrabet/sdk";
import { useEffect, useState } from "react";

export default function UsersWithRefresh() {
  const [client, setClient] = useState(null);
  const [timestamp, setTimestamp] = useState(null);

  // 初始化客户端
  useEffect(() => {
    fetchTokenAndCreateClient();
  }, []);

  // Token 过期检查
  useEffect(() => {
    if (!timestamp) return;

    const interval = setInterval(() => {
      // 检查 Token 是否即将过期(提前 1 分钟刷新)
      if (isTokenExpiring(timestamp, 60000)) {
        console.log("Token 即将过期,刷新中...");
        fetchTokenAndCreateClient();
      }
    }, 30000); // 每 30 秒检查一次

    return () => clearInterval(interval);
  }, [timestamp]);

  async function fetchTokenAndCreateClient() {
    const { token, timestamp: newTimestamp } = await fetch("/api/token").then(
      (r) => r.json()
    );

    const newClient = createClient({
      appCode: "your-app-code",
      token: token,
      timestamp: newTimestamp,
      models: {
        users: {
          tableName: "users",
          datasetCode: "your-dataset-code",
        },
        users: {
          tableName: "users",
          datasetCode: "your-dataset-code",
        },
      },
    });

    setClient(newClient);
    setTimestamp(newTimestamp);
  }

  // ... 其他代码
}

适用于已登录用户访问私有数据的场景。

TypeScript
"use client";

import { createClient } from "@lovrabet/sdk";
import { useEffect, useState } from "react";

export default function PrivateDataPage() {
  const [data, setData] = useState([]);

  useEffect(() => {
    async function fetchData() {
      // 不提供任何认证信息,自动使用浏览器 Cookie
      const client = createClient({
        appCode: "your-app-code",
        models: {
          orders: {
            tableName: "orders",
            datasetCode: "your-dataset-code",
          },
        },
      });

      // 请求会自动携带用户的登录 Cookie
      const { tableData } = await client.models.orders.filter();
      setData(tableData);
    }

    fetchData();
  }, []);

  return (
    <div>
      <h1>我的订单</h1>
      {data.map((order) => (
        <div key={order.id}>{order.orderNo}</div>
      ))}
    </div>
  );
}

多模型配置

一次性配置多个数据模型:

TypeScript
const client = createClient({
  appCode: "your-app-code",
  accessKey: process.env.LOVRABET_ACCESS_KEY,
  models: {
    users: {
      tableName: "users",
      datasetCode: "dataset-001",
    },
    orders: {
      tableName: "orders",
      datasetCode: "dataset-002",
    },
    products: {
      tableName: "products",
      datasetCode: "dataset-003",
    },
  },
});

// 使用不同的模型
const users = await client.models.users.filter();
const orders = await client.models.orders.filter();
const products = await client.models.products.filter();

高级查询

分页查询

TypeScript
const response = await client.models.users.filter({
  currentPage: 2,
  pageSize: 50,
});

console.log("当前页:", response.paging.currentPage);
console.log("每页条数:", response.paging.pageSize);
console.log("总条数:", response.paging.totalCount);
console.log("数据:", response.tableData);

条件筛选

TypeScript
const response = await client.models.users.filter({
  currentPage: 1,
  pageSize: 20,
  // 其他查询条件会传递到 paramMap
  status: "active",
  role: "admin",
});

获取所有数据(分页遍历)

TypeScript
async function getAllUsers() {
  const allUsers = [];
  let currentPage = 1;
  const pageSize = 50;
  let hasMore = true;

  while (hasMore) {
    const response = await client.models.users.filter({
      currentPage,
      pageSize,
    });

    allUsers.push(...response.tableData);

    // 判断是否还有更多数据
    hasMore = currentPage * pageSize < response.paging.totalCount;
    currentPage++;

    // 避免请求过快
    await new Promise((resolve) => setTimeout(resolve, 100));
  }

  return allUsers;
}

const allUsers = await getAllUsers();
console.log(`共获取 ${allUsers.length} 条数据`);

错误处理

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

try {
  const response = await client.models.users.filter();
  console.log(response.tableData);
} catch (error) {
  if (error instanceof LovrabetError) {
    console.error("API 错误:", error.message);
    console.error("状态码:", error.statusCode);
    console.error("错误详情:", error.details);
  } else {
    console.error("未知错误:", error);
  }
}

常见错误及解决方案

1. 签名验证失败

错误信息: errorCode: 1002, errorMsg: "签名验证失败"

解决方案:

  • 检查 Access Key 是否正确
  • 确保使用环境变量而非硬编码
  • 验证 appCode 和 datasetCode 是否匹配

2. Token 过期

错误信息: errorCode: 1003, errorMsg: "时间戳过期"

解决方案:

  • 确保客户端时间准确
  • 在浏览器端实现 Token 自动刷新机制
  • 使用 isTokenExpiring() 检查 Token 有效期

3. 无访问权限

错误信息: errorCode: 1006, errorMsg: "无访问权限"

解决方案:

  • 确认 Dataset Code 是否正确
  • 检查应用是否有该数据集的访问权限
  • 联系商务经理确认权限配置

环境配置

使用环境变量

创建 .env.local 文件:

Bash
LOVRABET_APP_CODE=your-app-code
LOVRABET_ACCESS_KEY=your-access-key
LOVRABET_DATASET_CODE=your-dataset-code

在代码中使用:

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

性能优化建议

1. 使用合理的分页大小

TypeScript
// ✅ 推荐:10-50 条
const response = await client.models.users.filter({
  pageSize: 20,
});

// ❌ 避免:单次查询过多数据
const response = await client.models.users.filter({
  pageSize: 1000, // 不推荐
});

2. 避免频繁调用 getOne

TypeScript
// ❌ 不推荐:循环调用 getOne
for (const id of userIds) {
  const user = await client.models.users.getOne(id);
}

// ✅ 推荐:使用 getList 批量查询
const users = await client.models.users.filter({
  id_in: userIds, // 假设支持 IN 查询
});

3. 实现缓存机制

TypeScript
const cache = new Map();

async function getCachedUsers() {
  const cacheKey = "users-list";

  if (cache.has(cacheKey)) {
    const { data, timestamp } = cache.get(cacheKey);
    // 缓存 1 分钟
    if (Date.now() - timestamp < 60000) {
      return data;
    }
  }

  const data = await client.models.users.filter();
  cache.set(cacheKey, { data, timestamp: Date.now() });

  return data;
}

底层实现(高级)

INFO

仅供参考以下是不使用 SDK 的底层实现方式。强烈推荐使用 SDK,除非您有特殊需求。

签名算法

TypeScript
import crypto from "crypto";

function generateToken(
  timestamp: number,
  appCode: string,
  datasetCode: string,
  accessKey: string,
  secretKey: string = "lovrabet"
): string {
  const params: Record<string, string> = {
    accessKey: accessKey,
    timeStamp: timestamp.toString(),
    appCode: appCode,
  };

  if (datasetCode) {
    params.datasetCode = datasetCode;
  }

  // 按字典序排序参数
  const sortedParams = Object.keys(params)
    .sort()
    .map((key) => `${key}=${params[key]}`)
    .join("&");

  // 计算 HMAC-SHA256
  return crypto
    .createHmac("sha256", secretKey)
    .update(sortedParams, "utf8")
    .digest("base64");
}

原始 HTTP 请求

TypeScript
import axios from "axios";

async function getListRaw() {
  const timestamp = Date.now();
  const appCode = "your-app-code";
  const datasetCode = "your-dataset-id";
  const accessKey = "your-access-key";

  const token = generateToken(timestamp, appCode, datasetCode, accessKey);

  const response = await axios.post(
    "https://runtime.lovrabet.com/openapi/data/get-list",
    {
      appCode: appCode,
      datasetCode: datasetCode,
      paramMap: {
        pageSize: 10,
        currentPage: 1,
      },
    },
    {
      headers: {
        "Content-Type": "application/json",
        "X-Time-Stamp": timestamp.toString(),
        "X-App-Code": appCode,
        "X-Dataset-Code": datasetCode,
        "X-Token": token,
      },
    }
  );

  return response.data;
}

下一步

恭喜!您已经掌握了 Lovrabet OpenAPI 的基本用法。接下来:

  • 认证指南 - 深入了解三种认证模式和 Token 管理
  • API 参考 - 查看完整的接口文档和高级功能

需要帮助?

如遇到问题:

  1. 查看本文档的常见错误部分
  2. 查看 GitHub 仓库 的 Issues
  3. 联系商务经理获取技术支持

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