跳到主要内容

API 使用指南

本文详细介绍 Lovrabet SDK 的 API 使用方法,包括 CRUD 操作、查询参数、响应处理等核心功能。

前置条件

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

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

📊 基础 CRUD 操作

查询列表

// 基础查询
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter();

// 带参数查询
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
currentPage: 1, // 当前页码
pageSize: 20, // 每页记录数
status: "active", // 自定义查询条件
keyword: "search-term", // 关键词搜索
});

// 响应结构
console.log(response.tableData); // 数据列表
console.log(response.total); // 总记录数
console.log(response.currentPage); // 当前页
console.log(response.pageSize); // 页大小

获取单条记录

const user = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getOne("user-id");
console.log(user); // 用户详情对象

创建记录

const newUser = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.create({
name: "Jane Doe",
email: "jane@example.com",
status: "active",
});

console.log("新创建的用户 ID:", newUser.id);
OpenAPI 模式支持 v1.1.14+

从 v1.1.14 开始,OpenAPI 模式也支持 create() 操作。之前版本仅 WebAPI 模式支持。

更新记录

const updatedUser = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.update("user-id", {
name: "Jane Smith",
email: "jane.smith@example.com",
});

console.log("更新后的用户:", updatedUser);
OpenAPI 模式支持 v1.1.14+

从 v1.1.14 开始,OpenAPI 模式也支持 update() 操作。之前版本仅 WebAPI 模式支持。

删除记录

await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.delete("user-id");
console.log("用户已删除");
操作限制

delete() 操作仅 WebAPI 模式(Cookie 认证)支持。OpenAPI 模式暂不支持删除操作。

导出 Excel v1.1.24+

将数据集导出为 Excel 文件,返回可供下载的文件 URL。

// 导出所有数据
const fileUrl = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.excelExport();
console.log(fileUrl);
// 'https://yuntoo-export-import.oss-cn-hangzhou.aliyuncs.com/xxx.xlsx?...'

// 在浏览器中打开下载
window.open(fileUrl, '_blank');

// 按条件筛选导出
const fileUrl = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.excelExport({
status: 'active',
createTime: '2025-01-01'
});
window.open(fileUrl, '_blank');
操作限制

excelExport() 操作仅 WebAPI 模式(Cookie 认证)支持。OpenAPI 模式暂不支持此功能。

获取下拉选项 v1.1.18+

用于获取数据表的下拉选项数据,适用于 Select、Radio、Checkbox 等表单组件。

重要概念:数据服务提供者模式

getSelectOptions() 是让数据表提供自身数据作为下拉选项,而不是查询其他表。

正确理解:

  • client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getSelectOptions() - 查询 users 表自己的数据,生成用户列表选项
  • 这些选项可以用在:订单表的"选择用户"字段、文章表的"选择作者"字段等

常见误解:

  • ❌ 在订单表中调用 client.models.dataset_a1b2c3d4e5f6789012345678abcdef12.getSelectOptions() 来获取用户列表
  • ✅ 应该调用 client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getSelectOptions() 来获取用户列表

核心原则:谁的数据,谁提供选项

这体现了重要的软件设计原则:

  • 🏗️ 单一职责原则(SRP) - 每个模型只负责管理和提供自己的数据
  • 🔌 依赖倒置原则(DIP) - 订单表依赖用户表提供的接口,而非直接耦合
  • 🎯 关注点分离(SoC) - 数据的提供者和消费者职责清晰分离
  • 📦 服务提供者模式 - 表作为数据源,向外部提供标准化的数据服务

这种设计使系统更易维护、扩展和测试。

// 获取用户表的下拉选项(查询 users 表自身数据)
const userOptions = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getSelectOptions({
code: "user_id", // 用作选项值的字段名
label: "user_name", // 用作显示文本的字段名
});

// 返回标准格式
console.log(userOptions);
// [
// { label: '张三', value: 'user001' },
// { label: '李四', value: 'user002' },
// { label: '王五', value: 'user003' }
// ]

// 在 React 中使用
import { Select } from "antd";

const { Option } = Select;

function UserSelector() {
const [options, setOptions] = useState([]);

useEffect(() => {
loadOptions();
}, []);

const loadOptions = async () => {
const data = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getSelectOptions({
code: "id",
label: "name",
});
setOptions(data);
};

return (
<Select placeholder="选择用户">
{options.map((option) => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
);
}

实际应用场景:

// 场景 1: 在订单表单中选择用户
// 调用 users 表提供用户选项
const userOptions = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getSelectOptions({
code: "user_id",
label: "user_name",
});
// 用在:<Select>选择下单用户</Select>

// 场景 2: 在文章表单中选择分类
// 调用 categories 表提供分类选项
const categoryOptions = await client.models.dataset_c1d2e3f4a5b6789012345678abcdef34.getSelectOptions({
code: "category_id",
label: "category_name",
});
// 用在:<Select>选择文章分类</Select>

// 场景 3: 在员工表单中选择部门
// 调用 departments 表提供部门选项
const deptOptions = await client.models.dataset_d1e2f3a4b5c6789012345678abcdef56.getSelectOptions({
code: "dept_id",
label: "dept_name",
});
// 用在:<Select>选择所属部门</Select>

// 场景 4: 在产品表单中选择供应商
// 调用 suppliers 表提供供应商选项
const supplierOptions = await client.models.dataset_e1f2a3b4c5d6789012345678abcdef78.getSelectOptions({
code: "supplier_id",
label: "supplier_name",
});
// 用在:<Select>选择供应商</Select>

记住核心原则: 哪个表的数据,就调用哪个表的 getSelectOptions()

操作限制

getSelectOptions() 操作仅 WebAPI 模式(Cookie 认证)支持。OpenAPI 模式暂不支持此功能。

执行自定义 SQL v1.1.19+

执行在 Lovrabet 平台上配置的自定义 SQL 查询,适用于复杂统计和聚合查询场景。

API 变更

从 v1.1.19 开始,推荐使用 client.sql.execute() 代替旧的 client.api.executeSql()。旧 API 仍然可用以保持向后兼容。

// 推荐方式:使用 client.sql 命名空间
const data = await client.sql.execute({
sqlCode: "fc8e7777-06e3847d"
});

// 检查执行结果
if (data.execSuccess && data.execResult) {
console.log(`查询成功,返回 ${data.execResult.length} 条数据`);
data.execResult.forEach((row) => {
console.log(row);
});
} else {
console.error("SQL 执行失败");
}

// 参数化查询(防止 SQL 注入)
const data = await client.sql.execute({
sqlCode: "fc8e7777-xxxxx",
params: {
userId: "123",
startDate: "2025-01-01",
endDate: "2025-12-31",
}
});

// 或使用直接传参方式(兼容)
const data = await client.sql.execute("fc8e7777-xxxxx", {
userId: "123",
startDate: "2025-01-01",
});

// 别名方式(向后兼容,不推荐)
const data = await client.api.executeSql("fc8e7777-xxxxx", {
userId: "123",
});

调用 BFF 后端函数 v1.3.0+

调用在 Lovrabet 平台上配置的 BFF (Backend For Frontend) 后端函数。

// 推荐方式:使用 client.bff 命名空间
const result = await client.bff.execute({
scriptName: 'calculatePrice',
params: { productId: '123', quantity: 10 }
});

// 带类型提示
interface PriceResult {
originalPrice: number;
discountedPrice: number;
discount: number;
}

const price = await client.bff.execute<PriceResult>({
scriptName: 'calculatePrice',
params: { productId: '123', quantity: 10 }
});
console.log(`原价: ${price.originalPrice}, 折后价: ${price.discountedPrice}`);

// 别名方式(向后兼容,不推荐)
const result = await client.api.bff('calculatePrice', {
productId: '123',
quantity: 10
});
详细使用指南

SQL API 和 BFF API 支持完整的 TypeScript 类型、错误处理等高级功能。详见:

🔍 高级查询功能

分页查询

// 基础分页
const page1 = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
currentPage: 1,
pageSize: 10,
});

// 大数据集的完整加载
const loadAllData = async () => {
let allData = [];
let currentPage = 1;
let hasMore = true;

while (hasMore) {
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
currentPage,
pageSize: 100, // 使用较大的页面大小提高效率
});

allData.push(...response.tableData);
hasMore = response.tableData.length === 100;
currentPage++;

// 避免无限循环
if (currentPage > 1000) break;
}

return allData;
};

条件查询

// 多条件查询
const activeUsers = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
status: "active",
role: "admin",
createdAfter: "2024-01-01",
currentPage: 1,
pageSize: 50,
});

// 关键词搜索
const searchResults = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
keyword: "john", // 搜索包含 "john" 的记录
pageSize: 20,
});

排序查询 v1.1.16+

使用 sortList 参数进行单字段或多字段排序:

// 单字段排序
const sortedUsers = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter(
{ currentPage: 1, pageSize: 20 },
[{ createTime: "desc" }] // 按创建时间降序
);

// 多字段排序
const complexSort = await client.models.dataset_f7e6d5c4b3a2901234567890fedcba98.filter(
{ currentPage: 1, pageSize: 50 },
[
{ priority: "desc" }, // 第一优先级:按优先级降序
{ createTime: "desc" }, // 第二优先级:按创建时间降序
{ name: "asc" }, // 第三优先级:按名称升序
]
);

高级过滤查询 (filter API) v1.1.22+

filter() 方法提供了比 getList() 更强大和灵活的查询能力,支持复杂的条件组合、范围查询、模糊匹配等高级功能。

全模式支持

从 v1.1.22 版本开始,filter API 同时支持 OpenAPI 模式WebAPI 模式

// 范围查询:查找年龄在 18-35 岁之间的用户
const youngUsers = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
where: {
age: {
$gte: 18, // 大于等于 18
$lte: 35, // 小于等于 35
},
},
pageSize: 50,
});

// 集合匹配:查找特定状态的订单
const pendingOrders = await client.models.dataset_a1b2c3d4e5f6789012345678abcdef12.filter({
where: {
status: { $in: ['pending', 'processing', 'confirmed'] },
},
sortList: [{ createTime: 'desc' }],
currentPage: 1,
pageSize: 20,
});

// 模糊搜索:搜索用户名包含特定关键词的用户
const searchUsers = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
where: {
username: { $contain: 'john' },
email: { $endWith: '@example.com' },
},
fields: ['id', 'username', 'email', 'createTime'],
});

// 复杂条件组合:使用 $and 和 $or
const complexQuery = await client.models.dataset_f7e6d5c4b3a2901234567890fedcba98.filter({
where: {
$and: [
{ price: { $gte: 100, $lte: 500 } },
{
$or: [
{ category: { $eq: 'electronics' } },
{ tags: { $contain: 'featured' } },
],
},
],
},
sortList: [{ price: 'asc' }],
pageSize: 30,
});

filter 相比 getList 的优势

  • 🎯 支持范围查询($gte$lte
  • 🔍 支持模糊匹配($contain$startWith$endWith
  • 🧮 支持集合操作($in$ne
  • 🔗 支持复杂逻辑组合($and$or
  • 📋 支持字段筛选(fields)减少数据传输

详细的操作符说明和更多示例,请参考 Filter API 完整指南

复杂查询示例

// 组合查询:获取最近注册的活跃用户
const recentActiveUsers = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter(
{
status: "active",
registeredAfter: new Date(
Date.now() - 30 * 24 * 60 * 60 * 1000
).toISOString(), // 30天内
currentPage: 1,
pageSize: 20,
},
[{ registeredAt: "desc" }] // 按注册时间降序
);

// 获取特定角色的用户统计
const getUsersByRole = async (role: string) => {
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
role,
pageSize: 1, // 只需要总数
});

return {
role,
count: response.total,
users: response.tableData,
};
};

📈 批量操作

批量创建

// 批量创建用户
const createBatchUsers = async (
users: Array<{ name: string; email: string }>
) => {
const results = [];

// 分批处理,避免单次请求过大
const batchSize = 10;
for (let i = 0; i < users.length; i += batchSize) {
const batch = users.slice(i, i + batchSize);

const batchResults = await Promise.all(
batch.map((user) => client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.create(user))
);

results.push(...batchResults);

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

return results;
};

批量更新

// 批量更新用户状态
const batchUpdateStatus = async (userIds: string[], status: string) => {
const updates = userIds.map((id) =>
client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.update(id, { status })
);

try {
const results = await Promise.all(updates);
console.log(`成功更新 ${results.length} 个用户`);
return results;
} catch (error) {
console.error("批量更新失败:", error);
throw error;
}
};

🔗 关联数据查询

获取关联数据

// 获取用户及其订单
const getUserWithOrders = async (userId: string) => {
// 获取用户信息
const user = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getOne(userId);

// 获取用户的订单
const orders = await client.models.dataset_a1b2c3d4e5f6789012345678abcdef12.filter({
userId: userId,
currentPage: 1,
pageSize: 50,
});

return {
user,
orders: orders.tableData,
totalOrders: orders.total,
};
};

// 获取订单及客户信息
const getOrderWithCustomer = async (orderId: string) => {
const order = await client.models.dataset_a1b2c3d4e5f6789012345678abcdef12.getOne(orderId);
const customer = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getOne(order.customerId);

return {
...order,
customer,
};
};

聚合查询模拟

// 用户订单统计
const getUserOrderStats = async (userId: string) => {
const orders = await client.models.dataset_a1b2c3d4e5f6789012345678abcdef12.filter({
userId,
pageSize: 1000, // 获取所有订单
});

const stats = {
totalOrders: orders.total,
totalAmount: 0,
averageAmount: 0,
statusBreakdown: {} as Record<string, number>,
};

// 计算统计信息
orders.tableData.forEach((order) => {
stats.totalAmount += order.amount || 0;
stats.statusBreakdown[order.status] =
(stats.statusBreakdown[order.status] || 0) + 1;
});

stats.averageAmount =
stats.totalOrders > 0 ? stats.totalAmount / stats.totalOrders : 0;

return stats;
};

⚠️ 错误处理

基础错误处理(try-catch 模式)

try {
const users = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter();
console.log("获取用户成功:", users);
} catch (error) {
console.error("获取用户失败:", error);

// 根据错误类型进行处理
if (error.status === 404) {
console.log("资源不存在");
} else if (error.status === 403) {
console.log("权限不足");
} else if (error.status === 500) {
console.log("服务器错误");
}
}

使用 safe 函数(无 try-catch)v1.3.0+

import { safe } from "@lovrabet/sdk";

// 简洁的错误处理
const { data, error } = await safe(() =>
client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter()
);

if (error) {
console.error("获取用户失败:", error.message, error.description);
return;
}

console.log("获取用户成功:", data);

并发请求的错误处理:

const [usersResult, ordersResult] = await Promise.all([
safe(() => client.models.users.filter()),
safe(() => client.models.orders.filter()),
]);

if (usersResult.error) {
console.error("用户加载失败");
}
if (ordersResult.error) {
console.error("订单加载失败");
}

// 使用数据
console.log("用户:", usersResult.data);
console.log("订单:", ordersResult.data);

高级错误处理

// 带重试的请求包装器
const withRetry = async <T>(
operation: () => Promise<T>,
maxRetries: number = 3,
delay: number = 1000
): Promise<T> => {
let lastError: any;

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

// 某些错误不需要重试
if (
error.status === 400 ||
error.status === 401 ||
error.status === 403
) {
throw error;
}

if (attempt < maxRetries) {
console.log(`请求失败,${delay}ms 后重试 (${attempt}/${maxRetries})`);
await new Promise((resolve) => setTimeout(resolve, delay));
delay *= 2; // 指数退避
}
}
}

throw lastError;
};

// 使用重试包装器
const robustGetUsers = () => withRetry(() => client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter());

请求状态管理

// 请求状态管理类
class RequestManager {
private loadingStates = new Map<string, boolean>();
private errors = new Map<string, any>();

async executeRequest<T>(
key: string,
operation: () => Promise<T>
): Promise<T> {
this.loadingStates.set(key, true);
this.errors.delete(key);

try {
const result = await operation();
return result;
} catch (error) {
this.errors.set(key, error);
throw error;
} finally {
this.loadingStates.set(key, false);
}
}

isLoading(key: string): boolean {
return this.loadingStates.get(key) || false;
}

getError(key: string): any {
return this.errors.get(key);
}
}

// 使用示例
const requestManager = new RequestManager();

const loadUsers = () =>
requestManager.executeRequest("users_list", () =>
client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter()
);

🎯 性能优化

请求缓存

// 简单的内存缓存
class SimpleCache {
private cache = new Map<string, { data: any; timestamp: number }>();
private ttl = 5 * 60 * 1000; // 5分钟TTL

set(key: string, data: any) {
this.cache.set(key, { data, timestamp: Date.now() });
}

get(key: string): any | null {
const item = this.cache.get(key);
if (!item) return null;

if (Date.now() - item.timestamp > this.ttl) {
this.cache.delete(key);
return null;
}

return item.data;
}

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

const cache = new SimpleCache();

// 带缓存的查询函数
const getCachedUsers = async (params: any = {}) => {
const cacheKey = `users_${JSON.stringify(params)}`;

// 尝试从缓存获取
let users = cache.get(cacheKey);
if (users) {
console.log("从缓存返回数据");
return users;
}

// 缓存未命中,从 API 获取
users = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter(params);
cache.set(cacheKey, users);
console.log("从 API 获取数据并缓存");

return users;
};

请求合并

// 请求去重和合并
class RequestDeduplicator {
private pendingRequests = new Map<string, Promise<any>>();

async request<T>(key: string, operation: () => Promise<T>): Promise<T> {
// 如果相同的请求正在进行中,直接返回该 Promise
if (this.pendingRequests.has(key)) {
return this.pendingRequests.get(key);
}

// 创建新的请求
const promise = operation().finally(() => {
// 请求完成后清除缓存
this.pendingRequests.delete(key);
});

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

const deduplicator = new RequestDeduplicator();

// 使用去重器
const getUsers = (params: any) =>
deduplicator.request(`users_${JSON.stringify(params)}`, () =>
client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter(params)
);

// 多次调用相同参数的请求会被合并
Promise.all([
getUsers({ status: "active" }),
getUsers({ status: "active" }),
getUsers({ status: "active" }),
]); // 实际只会发起一次请求

分页优化

// 渐进式加载
class InfiniteLoader {
private data: any[] = [];
private currentPage = 1;
private hasMore = true;
private loading = false;

async loadMore(pageSize: number = 20): Promise<any[]> {
if (this.loading || !this.hasMore) {
return this.data;
}

this.loading = true;

try {
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
currentPage: this.currentPage,
pageSize,
});

this.data.push(...response.tableData);
this.hasMore = response.tableData.length === pageSize;
this.currentPage++;

return this.data;
} finally {
this.loading = false;
}
}

reset() {
this.data = [];
this.currentPage = 1;
this.hasMore = true;
this.loading = false;
}

getData() {
return this.data;
}

isLoading() {
return this.loading;
}

canLoadMore() {
return this.hasMore && !this.loading;
}
}

📊 数据处理工具

数据转换

// 数据转换工具
const transformUserData = (rawUser: any) => ({
id: rawUser.id,
name: rawUser.name,
email: rawUser.email,
displayName: `${rawUser.name} (${rawUser.email})`,
isActive: rawUser.status === "active",
createdAt: new Date(rawUser.created_at),
avatar: rawUser.avatar_url || "/default-avatar.png",
});

// 批量转换
const transformUserList = (response: any) => ({
users: response.tableData.map(transformUserData),
pagination: {
current: response.currentPage,
size: response.pageSize,
total: response.total,
pages: Math.ceil(response.total / response.pageSize),
},
});

// 使用
const getUsersForUI = async () => {
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter();
return transformUserList(response);
};

数据验证

// 数据验证工具
const validateUserData = (userData: any) => {
const errors: string[] = [];

if (!userData.name || userData.name.trim().length === 0) {
errors.push("姓名不能为空");
}

if (!userData.email || !/\S+@\S+\.\S+/.test(userData.email)) {
errors.push("请输入有效的邮箱地址");
}

if (userData.age && (userData.age < 0 || userData.age > 120)) {
errors.push("年龄必须在 0-120 之间");
}

return {
isValid: errors.length === 0,
errors,
};
};

// 安全的创建用户
const createUserSafely = async (userData: any) => {
const validation = validateUserData(userData);

if (!validation.isValid) {
throw new Error(`数据验证失败: ${validation.errors.join(", ")}`);
}

return await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.create(userData);
};

📖 下一步

掌握 API 使用后,您可以继续学习:

❓ 常见问题

Q: 如何处理大数据量的查询?

建议使用分页查询,避免一次性加载过多数据:

// 推荐:分页查询
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
currentPage: 1,
pageSize: 50, // 根据实际需求调整
});

// 不推荐:获取所有数据
// const allUsers = await loadAllData(); // 可能导致性能问题

Q: API 调用失败时如何调试?

启用详细的错误日志:

const client = createClient({
options: {
onError: (error) => {
console.group("API Error Details");
console.log("Message:", error.message);
console.log("Status:", error.status);
console.log("URL:", error.url);
console.log("Request Data:", error.requestData);
console.log("Response Data:", error.responseData);
console.groupEnd();
},
},
});

Q: 如何实现乐观更新?

// 乐观更新示例
const optimisticUpdate = async (userId: string, updates: any) => {
// 1. 立即更新 UI(乐观更新)
updateUserInUI(userId, updates);

try {
// 2. 发送 API 请求
const updatedUser = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.update(userId, updates);

// 3. 用服务器响应的数据更新 UI
updateUserInUI(userId, updatedUser);

return updatedUser;
} catch (error) {
// 4. 如果失败,回滚 UI 状态
revertUserInUI(userId);
throw error;
}
};