跳到主要内容

常见问题

本文档汇总了使用 Lovrabet Java OpenSDK 过程中的常见问题和解决方案。


目录


认证与授权

Q1: 提示 "Token 无效" 或 "签名验证失败"

可能原因:

  1. AccessKey 不正确或已过期
  2. 系统时间与服务器时间相差过大(超过 10 分钟)
  3. 网络请求被代理或中间件修改

解决方案:

// 1. 验证 AccessKey 是否正确
System.out.println("AccessKey 前 10 位: " + accessKey.substring(0, 10));

// 2. 检查系统时间
System.out.println("当前时间戳: " + System.currentTimeMillis());
System.out.println("当前时间: " + new Date());

// 3. 确认请求参数正确
System.out.println("AppCode: " + request.getAppCode());
System.out.println("ModelCode: " + request.getModelCode());

如果时间不准确:

# Linux/Mac 同步系统时间
sudo ntpdate -u time.apple.com

# 或使用 NTP 服务
sudo systemctl start ntpd

Q2: 提示 "AccessKey 不存在或已失效"

解决方案:

  1. 登录 Lovrabet 云兔平台
  2. 进入应用管理,检查以下内容:
    • OpenAPI 功能是否已开通
    • AccessKey 是否已生成
    • AccessKey 是否在有效期内
  3. 如果 AccessKey 过期或丢失,重新生成:
    • 进入应用设置
    • 找到 OpenAPI 配置
    • 点击"重新生成 AccessKey"
    • 复制新的 AccessKey 并更新配置

注意: 重新生成 AccessKey 后,旧的 AccessKey 立即失效,需要更新所有使用该 AccessKey 的应用。


Q3: 提示 "应用编码不存在"

解决方案:

  1. 检查 appCode 是否正确:
// appCode 格式通常为: app-xxxxxxxx
String appCode = "app-c2dd52a2"; // 确保格式正确

// 检查是否有多余的空格或换行符
appCode = appCode.trim();
  1. 在 Lovrabet 云兔平台确认应用编码:
    • 进入应用管理
    • 查看应用详情
    • 复制正确的应用编码

Q4: 多个应用如何管理 AccessKey?

推荐方案:

方式 1: 环境变量 + 配置文件

# application.yml
lovrabet:
app1:
access-key: ${APP1_ACCESS_KEY}
app-code: app-xxx1
app2:
access-key: ${APP2_ACCESS_KEY}
app-code: app-xxx2

方式 2: 使用密钥管理服务

@Configuration
public class LovrabetMultiAppConfig {

@Bean("app1Client")
public LovrabetSDKClient app1Client(SecretManager secretManager) {
String accessKey = secretManager.getSecret("lovrabet-app1-key");
return new LovrabetSDKClient(accessKey, baseUrl);
}

@Bean("app2Client")
public LovrabetSDKClient app2Client(SecretManager secretManager) {
String accessKey = secretManager.getSecret("lovrabet-app2-key");
return new LovrabetSDKClient(accessKey, baseUrl);
}
}

参数与数据

Q5: 创建数据时提示 "必填字段缺失"

解决方案:

  1. 登录 Lovrabet 云兔平台,查看数据集的字段定义
  2. 确认哪些字段是必填的
  3. 确保所有必填字段都已提供值:
Map<String, Object> paramMap = new HashMap<>();

// ✅ 正确:提供必填字段的值
paramMap.put("TEXT_1", "客户名称");
paramMap.put("NUMBER_3", 100);

// ❌ 错误:不要传递 null 值
// paramMap.put("TEXT_2", null);

// 对于非必填的空值字段,直接不放入 paramMap

Q6: 字段名不确定,如何查看数据集的字段定义?

方法 1: 在 Lovrabet 云兔平台查看

  1. 进入应用管理
  2. 选择对应的数据集
  3. 查看字段列表,获取字段编码(如 TEXT_1, NUMBER_3)

方法 2: 通过浏览器开发者工具

  1. 在 Lovrabet 云兔平台打开数据集
  2. 打开浏览器开发者工具(F12)
  3. 切换到 Network 标签
  4. 创建或编辑一条数据
  5. 查看请求参数,了解字段名和数据格式

字段命名规则:

  • TEXT_1, TEXT_2 - 文本字段
  • NUMBER_1, NUMBER_2 - 数字字段
  • SELECT_1, SELECT_2 - 选择字段
  • DATETIME_1, DATETIME_2 - 日期时间字段
  • BOOLEAN_1, BOOLEAN_2 - 布尔字段

Q7: 如何处理日期时间字段?

方案 1: 使用时间戳(推荐)

// 当前时间
paramMap.put("DATETIME_1", System.currentTimeMillis());

// 指定日期
Calendar calendar = Calendar.getInstance();
calendar.set(2025, Calendar.JANUARY, 12, 10, 30, 0);
paramMap.put("DATETIME_1", calendar.getTimeInMillis());

// 从 Date 对象
Date date = new Date();
paramMap.put("DATETIME_1", date.getTime());

方案 2: 使用字符串格式

// 格式: yyyy-MM-dd HH:mm:ss
paramMap.put("DATETIME_1", "2025-01-12 10:30:00");

读取日期时间:

// API 返回的是时间戳(毫秒)
Number timestampMs = (Number) data.get("DATETIME_1");
if (timestampMs != null) {
Date date = new Date(timestampMs.longValue());
System.out.println("创建时间: " + date);
}

Q8: 更新数据时,是否需要传递所有字段?

不需要,只需要传递要更新的字段:

// ✅ 推荐:只传递要更新的字段
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("TEXT_1", "新的客户名称"); // 只更新客户名称

LovrabetRequest request = new LovrabetRequest();
request.setId(513L);
request.setParamMap(paramMap);

sdkClient.update(request);

注意: ID 字段会被 SDK 自动添加到 paramMap 中,无需手动添加。


Q9: 如何处理选择字段(下拉框)?

单选字段:

// 选择字段的值可能是字符串或数字,取决于数据集定义
paramMap.put("SELECT_4", "china-north"); // 字符串类型
paramMap.put("SELECT_7", 463); // 数字类型

多选字段:

// 多选字段使用 List
List<String> regions = Arrays.asList("north", "south", "east");
paramMap.put("MULTI_SELECT_1", regions);

查询选择字段的可选值:

在 Lovrabet 云兔平台的数据集字段定义中查看选项列表。


Q10: 分页查询返回数据为空,但数据集确实有数据

可能原因:

  1. 页码超出实际数据范围
  2. 查询条件过滤掉了所有数据
  3. 数据权限限制

排查步骤:

// 1. 先查询第一页
paramMap.put("currentPage", 1);
paramMap.put("pageSize", 10);

LovrabetResult<?> result = sdkClient.getList(request);

if (result.isSuccess()) {
List<?> dataList = (List<?>) result.getData();
System.out.println("第一页数据量: " + dataList.size());

if (dataList.isEmpty()) {
// 2. 检查查询条件
System.out.println("查询参数: " + paramMap);

// 3. 尝试不带任何查询条件
Map<String, Object> simpleParams = new HashMap<>();
simpleParams.put("currentPage", 1);
simpleParams.put("pageSize", 10);
request.setParamMap(simpleParams);

LovrabetResult<?> result2 = sdkClient.getList(request);
// ...
}
}

性能问题

Q11: 查询速度很慢,如何优化?

优化策略:

1. 减少单次查询数据量

// ❌ 不推荐:pageSize 过大
paramMap.put("pageSize", 1000);

// ✅ 推荐:使用合理的 pageSize
paramMap.put("pageSize", 50); // 20-100 之间

2. 使用本地缓存

// 对于不常变化的数据,使用缓存
private final Cache<String, Map<String, Object>> cache =
Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();

public Map<String, Object> getData(String id) {
return cache.get(id, key -> fetchFromApi(key));
}

3. 并发查询

// 使用线程池并发查询多个数据
ExecutorService executor = Executors.newFixedThreadPool(10);
List<Future<Map<String, Object>>> futures = new ArrayList<>();

for (String id : idList) {
Future<Map<String, Object>> future = executor.submit(() -> {
return queryData(id);
});
futures.add(future);
}

// 收集结果
for (Future<Map<String, Object>> future : futures) {
Map<String, Object> data = future.get();
// 处理数据
}

executor.shutdown();

Q12: 大量数据创建时效率低,如何提高?

优化方案:

1. 使用线程池并发创建

ExecutorService executor = Executors.newFixedThreadPool(10);  // 控制并发数

List<CompletableFuture<Void>> futures = dataList.stream()
.map(data -> CompletableFuture.runAsync(() -> {
try {
LovrabetRequest request = buildRequest(data);
sdkClient.create(request);
} catch (Exception e) {
logger.error("创建失败", e);
}
}, executor))
.collect(Collectors.toList());

// 等待所有任务完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
executor.shutdown();

2. 批量处理 + 错误重试

public void batchCreateWithRetry(List<Map<String, Object>> dataList) {
int batchSize = 100;
for (int i = 0; i < dataList.size(); i += batchSize) {
int end = Math.min(i + batchSize, dataList.size());
List<Map<String, Object>> batch = dataList.subList(i, end);

// 批量创建
batchCreate(batch);

// 短暂休息,避免过载
Thread.sleep(100);
}
}

Q13: 内存占用过高

可能原因:

  1. 一次性加载过多数据
  2. 没有及时释放资源

解决方案:

1. 流式处理大量数据

// ❌ 不推荐:一次性加载所有数据
List<Map<String, Object>> allData = getAllData(); // 可能有 10 万条

// ✅ 推荐:分页流式处理
public void processAllData(Consumer<Map<String, Object>> processor) {
int currentPage = 1;
int pageSize = 100;
boolean hasMore = true;

while (hasMore) {
List<Map<String, Object>> pageData = getPageData(currentPage, pageSize);

if (pageData.isEmpty()) {
hasMore = false;
} else {
// 处理当前页数据
pageData.forEach(processor);

// 释放引用,帮助 GC
pageData.clear();

if (pageData.size() < pageSize) {
hasMore = false;
} else {
currentPage++;
}
}
}
}

2. 及时清理缓存

// 定期清理缓存
@Scheduled(fixedRate = 3600000) // 每小时清理一次
public void cleanupCache() {
cache.cleanUp();
logger.info("缓存清理完成");
}

网络问题

Q14: 提示 "Connection timeout" 或 "Read timeout"

可能原因:

  1. 网络不稳定
  2. 服务器响应慢
  3. 请求数据量过大

解决方案:

1. 检查网络连通性

# 测试网络连接
ping runtime.lovrabet.com

# 测试 HTTPS 连接
curl -I https://runtime.lovrabet.com/openapi

2. 实现重试机制

// 使用重试工具(参考最佳实践文档)
LovrabetResult<?> result = RetryHelper.executeWithRetry(
() -> sdkClient.getOne(request),
3, // 最多重试 3 次
1000L // 初始延迟 1 秒
);

3. 减少单次请求的数据量

// 如果查询数据量过大导致超时
paramMap.put("pageSize", 20); // 减小 pageSize

Q15: 在公司内网无法访问 Lovrabet API

可能原因: 公司防火墙或代理限制

解决方案:

1. 配置代理

// 设置系统代理
System.setProperty("https.proxyHost", "proxy.company.com");
System.setProperty("https.proxyPort", "8080");

// 如果代理需要认证
System.setProperty("https.proxyUser", "username");
System.setProperty("https.proxyPassword", "password");

2. 联系网络管理员

需要将以下域名加入白名单:

  • runtime.lovrabet.com
  • 端口: 443 (HTTPS)

Q16: 偶尔出现 "SocketException: Connection reset"

原因: 网络抖动或连接被重置

解决方案:

实现自动重试机制(参考 最佳实践 - 错误处理):

LovrabetResult<?> result = RetryHelper.executeWithRetry(
() -> sdkClient.getList(request),
3, // 最多重试 3 次
2000L // 初始延迟 2 秒
);

数据一致性

Q17: 更新数据后查询不到最新值

可能原因:

  1. 数据库主从同步延迟
  2. 本地缓存未更新

解决方案:

1. 等待短暂时间后查询

// 更新数据
sdkClient.update(updateRequest);

// 短暂等待(通常 100-200ms 足够)
Thread.sleep(100);

// 查询数据
LovrabetResult<?> result = sdkClient.getOne(queryRequest);

2. 使用更新返回的数据

// 更新后直接使用本地数据,不重新查询
Map<String, Object> localData = updateRequest.getParamMap();
// 使用 localData 而不是重新查询

3. 清除本地缓存

// 更新后清除缓存
sdkClient.update(request);
cache.invalidate(id); // 清除该 ID 的缓存

Q18: 并发更新同一条数据,如何避免冲突?

方案 1: 乐观锁(推荐)

public boolean updateWithVersion(Long id, Map<String, Object> newData) {
// 1. 查询当前数据和版本号
Map<String, Object> current = getData(id);
Integer currentVersion = (Integer) current.get("version");

// 2. 更新时携带版本号
newData.put("version", currentVersion + 1);

LovrabetRequest request = new LovrabetRequest();
request.setId(id);
request.setParamMap(newData);

LovrabetResult<String> result = sdkClient.update(request);

// 3. 检查更新结果
if (!result.isSuccess()) {
// 版本冲突,可能需要重试
logger.warn("版本冲突,当前版本: {}", currentVersion);
return false;
}

return true;
}

方案 2: 分布式锁

public void updateWithLock(Long id, Map<String, Object> newData) {
String lockKey = "lovrabet:lock:" + id;

// 获取分布式锁
RLock lock = redissonClient.getLock(lockKey);

try {
// 尝试获取锁,最多等待 10 秒,锁超时时间 30 秒
if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
try {
// 执行更新操作
sdkClient.update(buildRequest(id, newData));
} finally {
lock.unlock();
}
} else {
logger.warn("获取锁超时: {}", id);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}

开发调试

Q19: 如何查看 SDK 发送的 HTTP 请求详情?

方法 1: 启用日志

# application.yml 或 logback.xml
logging:
level:
com.lovrabet.runtime.opensdk: DEBUG

方法 2: 使用抓包工具

  • Windows: Fiddler
  • Mac: Charles Proxy
  • 跨平台: Wireshark

配置代理:

System.setProperty("https.proxyHost", "localhost");
System.setProperty("https.proxyPort", "8888"); // Charles 默认端口

Q20: 如何在本地开发环境测试 SDK?

方案 1: 使用测试数据集

在 Lovrabet 云兔平台创建专门的测试数据集,避免污染生产数据。

# application-dev.yml
lovrabet:
app-code: app-dev-xxx
model-code: test-dataset-xxx

方案 2: Mock SDK 客户端

@TestConfiguration
public class TestConfig {

@Bean
@Primary // 覆盖正常的 Bean
public LovrabetSDKClient mockSdkClient() {
LovrabetSDKClient mockClient = Mockito.mock(LovrabetSDKClient.class);

// 设置 Mock 行为
LovrabetResult<String> mockResult = new LovrabetResult<>();
mockResult.setSuccess(true);
mockResult.setData("mock-id-12345");

Mockito.when(mockClient.create(Mockito.any()))
.thenReturn(mockResult);

return mockClient;
}
}

Q21: 如何排查 "数据创建成功但查询不到" 的问题?

排查步骤:

// 1. 创建数据并记录返回的 ID
LovrabetResult<String> createResult = sdkClient.create(request);
if (createResult.isSuccess()) {
String newId = createResult.getData();
logger.info("创建成功,新 ID: {}", newId);

// 2. 立即查询确认
Thread.sleep(200); // 等待 200ms

LovrabetRequest queryRequest = new LovrabetRequest();
queryRequest.setAppCode(appCode);
queryRequest.setModelCode(modelCode);
queryRequest.setId(Long.parseLong(newId));

LovrabetResult<?> queryResult = sdkClient.getOne(queryRequest);

if (queryResult.isSuccess()) {
Map<String, Object> data = (Map<String, Object>) queryResult.getData();
logger.info("查询成功: {}", data);
} else {
logger.error("查询失败: {}", queryResult.getResultMsg());
// 3. 检查 appCode 和 modelCode 是否一致
logger.error("创建时 appCode: {}, modelCode: {}",
request.getAppCode(), request.getModelCode());
logger.error("查询时 appCode: {}, modelCode: {}",
queryRequest.getAppCode(), queryRequest.getModelCode());
}
}

Q22: SDK 版本如何升级?

Maven 项目:

<dependency>
<groupId>com.lovrabet.runtime</groupId>
<artifactId>lovrabet-runtime-opensdk</artifactId>
<version>1.1.0-SNAPSHOT</version> <!-- 更新版本号 -->
</dependency>

然后执行:

mvn clean install

Gradle 项目:

implementation 'com.lovrabet.runtime:lovrabet-runtime-opensdk:1.1.0-SNAPSHOT'

然后执行:

./gradlew clean build

升级注意事项:

  1. 查看官方文档了解变更内容
  2. 在测试环境验证后再升级生产环境
  3. 注意是否有不兼容的变更

获取帮助

如果以上问题没有解决您的疑问,请通过以下方式获取帮助:

技术支持

反馈问题时,请提供以下信息:

  1. SDK 版本: lovrabet-runtime-opensdk 的版本号
  2. JDK 版本: 使用的 Java 版本
  3. 错误信息: 完整的错误堆栈(注意脱敏敏感信息)
  4. 复现步骤: 如何触发该问题
  5. 代码片段: 相关的代码示例(脱敏后)

示例邮件:

主题: [OpenSDK] 创建数据时提示必填字段缺失

您好,

我在使用 Lovrabet Java OpenSDK 时遇到问题:

1. SDK 版本: lovrabet-runtime-opensdk 1.0.0-SNAPSHOT
2. JDK 版本: Java 11
3. 错误信息:
resultCode: PARAM_ERROR
resultMsg: 必填字段 TEXT_1 缺失

4. 代码片段:
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("TEXT_1", "测试数据");
// ...

5. 已尝试的解决方法:
- 确认字段名正确
- 确认值不为 null

请问这个问题如何解决?

谢谢!

下一步

查阅完常见问题后,您可以按以下顺序回顾学习内容

  1. 📖 快速开始 5 分钟完成第一个 CRUD 程序

  2. 📚 核心概念 深入理解 SDK 工作原理

  3. 📋 API 参考 查阅完整的接口文档

  4. 💡 业务示例 学习 5 个真实场景的实现

  5. 🚀 最佳实践 掌握生产环境优化技巧

  6. 常见问题 ← 当前位置 解决使用中的疑问


最后更新: 2025-10-12