最佳实践
本文档介绍在生产环境中使用 Lovrabet Java OpenSDK 的最佳实践,帮助您构建高性能、高可用的应用。
目录
1. 客户端管理
1.1 使用单例模式
LovrabetSDKClient 是线程安全的,建议在应用中创建单例复用,避免频繁创建实例。
✅ 推荐做法
package com.example.config;
import com.lovrabet.runtime.opensdk.client.LovrabetSDKClient;
/**
* SDK 客户端单例管理器
*/
public class SdkClientManager {
private static volatile LovrabetSDKClient instance;
private SdkClientManager() {
// 私有构造函数,防止外部实例化
}
public static LovrabetSDKClient getInstance() {
if (instance == null) {
synchronized (SdkClientManager.class) {
if (instance == null) {
String accessKey = ConfigManager.getAccessKey();
String baseUrl = ConfigManager.getBaseUrl();
instance = new LovrabetSDKClient(accessKey, baseUrl);
}
}
}
return instance;
}
}
❌ 不推荐做法
// 不推荐:每次调用都创建新实例
public void someMethod() {
LovrabetSDKClient client = new LovrabetSDKClient(accessKey, baseUrl);
// ...
}
1.2 Spring Boot 环境下的配置
使用 Spring 的依赖注入管理 SDK 客户端:
@Configuration
public class LovrabetConfig {
@Value("${lovrabet.access-key}")
private String accessKey;
@Value("${lovrabet.base-url}")
private String baseUrl;
@Bean
public LovrabetSDKClient lovrabetSDKClient() {
return new LovrabetSDKClient(accessKey, baseUrl);
}
}
@Service
public class DataService {
private final LovrabetSDKClient sdkClient;
// 通过构造函数注入
public DataService(LovrabetSDKClient sdkClient) {
this.sdkClient = sdkClient;
}
}
2. 安全性
2.1 AccessKey 管理
✅ 推荐做法
方式 1: 使用环境变量
String accessKey = System.getenv("LOVRABET_ACCESS_KEY");
if (accessKey == null || accessKey.isEmpty()) {
throw new IllegalStateException("LOVRABET_ACCESS_KEY 环境变量未设置");
}
# 在系统环境中设置
export LOVRABET_ACCESS_KEY="ak-your-access-key"
# 或者在启动 Java 应用时传递
java -jar app.jar
方式 2: 使用配置文件 + 配置中心
# application.yml
lovrabet:
access-key: ${LOVRABET_ACCESS_KEY:}
base-url: https://runtime.lovrabet.com/openapi
@Value("${lovrabet.access-key}")
private String accessKey;
方式 3: 使用密钥管理服务
// 使用云服务商的密钥管理服务
String accessKey = secretManager.getSecret("lovrabet-access-key");
❌ 不推荐做法
// ❌ 不要硬编码在代码中
String accessKey = "ak-OD8xWhMOsL5ChQ3Akhv4uYYiu1fPOFQGVF9BULIeov8";
// ❌ 不要提交到代码仓库
// git add config.properties (包含明文 accessKey)
// ❌ 不要在日志中打印
logger.info("AccessKey: " + accessKey); // 危险!
// ❌ 不要在异常信息中暴露
throw new Exception("AccessKey " + accessKey + " 无效");
2.2 数据传输安全
// ✅ 始终使用 HTTPS
String baseUrl = "https://runtime.lovrabet.com/openapi";
// ❌ 不要使用 HTTP
// String baseUrl = "http://runtime.lovrabet.com/openapi";
2.3 敏感数据处理
// 在日志中脱敏敏感信息
public void logCustomerInfo(Map<String, Object> customer) {
String phone = (String) customer.get("TEXT_2");
// 脱敏手机号:138****1234
String maskedPhone = phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
logger.info("客户电话: {}", maskedPhone);
}
3. 性能优化
3.1 分页查询优化
选择合适的 pageSize
// ✅ 推荐:根据实际需求选择合适的 pageSize
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("pageSize", 50); // 20-100 之间较为合理
paramMap.put("currentPage", 1);
// ❌ 不推荐:pageSize 过小,导致请求次数过多
paramMap.put("pageSize", 5);
// ❌ 不推荐:pageSize 过大,导致单次响应慢、内存占用高
paramMap.put("pageSize", 10000);
避免深度分页
// ❌ 避免查询过深的页码
if (currentPage > 100) {
logger.warn("页码过深可能影响性能: {}", currentPage);
// 考虑使用其他方式,如基于游标的分页
}
3.2 批量操作优化
使用线程池并发处理
import java.util.concurrent.*;
/**
* 批量创建数据 - 使用线程池
*/
public void batchCreate(List<Map<String, Object>> dataList) {
// 创建固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
List<Future<Boolean>> futures = new ArrayList<>();
for (Map<String, Object> data : dataList) {
Future<Boolean> future = executor.submit(() -> {
try {
LovrabetRequest request = buildRequest(data);
LovrabetResult<String> result = sdkClient.create(request);
return result.isSuccess();
} catch (Exception e) {
logger.error("创建数据失败", e);
return false;
}
});
futures.add(future);
}
// 等待所有任务完成
int successCount = 0;
for (Future<Boolean> future : futures) {
try {
if (future.get(30, TimeUnit.SECONDS)) {
successCount++;
}
} catch (Exception e) {
logger.error("获取任务结果失败", e);
}
}
executor.shutdown();
logger.info("批量创建完成,成功: {}/{}", successCount, dataList.size());
}
控制并发数量
// ✅ 推荐:控制并发数避免过载服务器
ExecutorService executor = Executors.newFixedThreadPool(10); // 限制 10 个并发
// ❌ 不推荐:无限制并发可能导致服务器过载
// ExecutorService executor = Executors.newCachedThreadPool();
3.3 缓存策略
对于不常变化的数据,使用本地缓存减少 API 调用:
import com.github.benmanes.caffeine.cache.*;
/**
* 带缓存的数据服务
*/
public class CachedDataService {
private final LovrabetSDKClient sdkClient;
// 使用 Caffeine 缓存
private final Cache<String, Map<String, Object>> cache = Caffeine.newBuilder()
.maximumSize(1000) // 最大缓存 1000 条
.expireAfterWrite(5, TimeUnit.MINUTES) // 5 分钟后过期
.build();
public Map<String, Object> getData(String id) {
// 先从缓存获取
return cache.get(id, key -> {
// 缓存未命中,从 API 获取
logger.debug("缓存未命中,从 API 获取数据: {}", id);
return fetchFromApi(id);
});
}
private Map<String, Object> fetchFromApi(String id) {
LovrabetRequest request = new LovrabetRequest();
request.setAppCode(appCode);
request.setModelCode(modelCode);
request.setId(Long.parseLong(id));
LovrabetResult<?> result = sdkClient.getOne(request);
if (result.isSuccess()) {
return (Map<String, Object>) result.getData();
}
return null;
}
// 更新数据时清除缓存
public void updateData(String id, Map<String, Object> data) {
// 更新 API
updateApi(id, data);
// 清除缓存
cache.invalidate(id);
}
}
3.4 连接复用
SDK 内部已经使用 HTTP 连接池,无需额外配置。但要注意:
// ✅ 推荐:复用 SDK 客户端实例
LovrabetSDKClient client = SdkClientManager.getInstance();
// ❌ 不推荐:频繁创建和销毁客户端
for (int i = 0; i < 1000; i++) {
LovrabetSDKClient client = new LovrabetSDKClient(accessKey, baseUrl);
// ...
}
4. 错误处理
4.1 统一错误处理
创建统一的错误处理工具类:
package com.example.util;
import com.lovrabet.runtime.opensdk.model.LovrabetResult;
/**
* SDK 结果处理工具
*/
public class ResultHandler {
/**
* 处理结果,失败时抛出异常
*/
public static <T> T unwrap(LovrabetResult<T> result) {
if (result == null) {
throw new LovrabetException("API 响应为空");
}
if (result.isSuccess()) {
return result.getData();
} else {
throw new LovrabetException(
result.getResultCode(),
result.getResultMsg()
);
}
}
/**
* 处理结果,失败时返回默认值
*/
public static <T> T unwrapOrDefault(LovrabetResult<T> result, T defaultValue) {
if (result == null || !result.isSuccess()) {
return defaultValue;
}
return result.getData();
}
/**
* 处理结果,失败时返回 Optional
*/
public static <T> Optional<T> unwrapOptional(LovrabetResult<T> result) {
if (result != null && result.isSuccess()) {
return Optional.ofNullable(result.getData());
}
return Optional.empty();
}
}
/**
* 自定义异常类
*/
public class LovrabetException extends RuntimeException {
private final String errorCode;
public LovrabetException(String message) {
super(message);
this.errorCode = "UNKNOWN";
}
public LovrabetException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public String getErrorCode() {
return errorCode;
}
}
使用示例:
// 方式 1: 失败时抛出异常
try {
Map<String, Object> data = ResultHandler.unwrap(result);
// 处理数据
} catch (LovrabetException e) {
logger.error("API 调用失败 [{}]: {}", e.getErrorCode(), e.getMessage());
}
// 方式 2: 失败时使用默认值
List<Map<String, Object>> dataList =
ResultHandler.unwrapOrDefault(result, Collections.emptyList());
// 方式 3: 使用 Optional
ResultHandler.unwrapOptional(result)
.ifPresent(data -> processData(data));
4.2 重试机制
对于临时性错误(如网络抖动),实现自动重试:
package com.example.util;
import java.util.function.Supplier;
/**
* 重试工具类
*/
public class RetryHelper {
/**
* 带指数退避的重试
*/
public static <T> LovrabetResult<T> executeWithRetry(
Supplier<LovrabetResult<T>> operation,
int maxRetries,
long initialDelayMs) {
int attempt = 0;
long delay = initialDelayMs;
while (attempt <= maxRetries) {
try {
LovrabetResult<T> result = operation.get();
// 成功则返回
if (result != null && result.isSuccess()) {
if (attempt > 0) {
logger.info("重试成功,尝试次数: {}", attempt + 1);
}
return result;
}
// 已达最大重试次数
if (attempt == maxRetries) {
logger.error("达到最大重试次数 {},操作失败", maxRetries);
return result;
}
// 判断是否需要重试
if (!shouldRetry(result)) {
logger.warn("错误不可重试,直接返回");
return result;
}
// 等待后重试
logger.warn("操作失败,{} ms 后进行第 {} 次重试", delay, attempt + 2);
Thread.sleep(delay);
// 指数退避:每次重试间隔翻倍
delay *= 2;
attempt++;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("重试被中断", e);
} catch (Exception e) {
logger.error("执行操作时发生异常", e);
if (attempt == maxRetries) {
throw new RuntimeException("重试后仍然失败", e);
}
attempt++;
}
}
throw new RuntimeException("不应该到达这里");
}
/**
* 判断错误是否可重试
*/
private static <T> boolean shouldRetry(LovrabetResult<T> result) {
if (result == null) {
return true; // 空结果可重试
}
String errorCode = result.getResultCode();
// 以下错误码不应重试
if ("INVALID_PARAM".equals(errorCode) ||
"ACCESS_DENIED".equals(errorCode) ||
"RESOURCE_NOT_FOUND".equals(errorCode)) {
return false;
}
// 其他错误可重试
return true;
}
}
使用示例:
// 最多重试 3 次,初始间隔 1000ms,使用指数退避
LovrabetResult<?> result = RetryHelper.executeWithRetry(
() -> sdkClient.getOne(request),
3, // 最多重试 3 次
1000L // 初始延迟 1 秒
);
4.3 超时处理
对于长时间运行的操作,设置超时机制:
import java.util.concurrent.*;
/**
* 带超时的操作执行
*/
public <T> LovrabetResult<T> executeWithTimeout(
Supplier<LovrabetResult<T>> operation,
long timeoutSeconds) {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
Future<LovrabetResult<T>> future = executor.submit(operation::get);
return future.get(timeoutSeconds, TimeUnit.SECONDS);
} catch (TimeoutException e) {
logger.error("操作超时,超过 {} 秒", timeoutSeconds);
throw new RuntimeException("操作超时", e);
} catch (Exception e) {
logger.error("操作执行失败", e);
throw new RuntimeException("操作失败", e);
} finally {
executor.shutdown();
}
}
5. 日志管理
5.1 结构化日志
使用结构化日志便于后续分析:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DataService {
private static final Logger logger = LoggerFactory.getLogger(DataService.class);
public void createData(Map<String, Object> data) {
long startTime = System.currentTimeMillis();
String operation = "createData";
try {
logger.info("开始操作 - operation={}, dataSize={}",
operation, data.size());
LovrabetResult<String> result = sdkClient.create(request);
long duration = System.currentTimeMillis() - startTime;
if (result.isSuccess()) {
logger.info("操作成功 - operation={}, id={}, duration={}ms",
operation, result.getData(), duration);
} else {
logger.error("操作失败 - operation={}, errorCode={}, errorMsg={}, duration={}ms",
operation, result.getResultCode(), result.getResultMsg(), duration);
}
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
logger.error("操作异常 - operation={}, duration={}ms",
operation, duration, e);
throw e;
}
}
}
5.2 日志级别
合理使用日志级别:
// DEBUG: 详细的调试信息
logger.debug("请求参数: {}", paramMap);
// INFO: 重要的业务流程信息
logger.info("开始同步客户数据,数量: {}", customers.size());
// WARN: 警告信息,不影响主流程
logger.warn("数据字段 {} 为空,将使用默认值", fieldName);
// ERROR: 错误信息,需要关注
logger.error("创建数据失败: {}", result.getResultMsg());
// ERROR with exception: 异常堆栈
logger.error("处理数据时发生异常", e);
5.3 敏感信息脱敏
/**
* 日志脱敏工具
*/
public class LogMaskUtil {
/**
* 脱敏手机号
*/
public static String maskPhone(String phone) {
if (phone == null || phone.length() < 11) {
return phone;
}
return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
}
/**
* 脱敏邮箱
*/
public static String maskEmail(String email) {
if (email == null || !email.contains("@")) {
return email;
}
String[] parts = email.split("@");
String username = parts[0];
if (username.length() <= 2) {
return email;
}
String masked = username.substring(0, 2) + "***" +
username.substring(username.length() - 1);
return masked + "@" + parts[1];
}
/**
* 脱敏 AccessKey
*/
public static String maskAccessKey(String accessKey) {
if (accessKey == null || accessKey.length() < 20) {
return "***";
}
return accessKey.substring(0, 10) + "***";
}
}
使用示例:
logger.info("客户信息 - 姓名: {}, 电话: {}, 邮箱: {}",
customer.getName(),
LogMaskUtil.maskPhone(customer.getPhone()),
LogMaskUtil.maskEmail(customer.getEmail())
);
6. 测试策略
6.1 单元测试
使用 Mock 框架进行单元测试:
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
public class CustomerServiceTest {
@Test
public void testCreateCustomer() {
// 创建 Mock 对象
LovrabetSDKClient mockClient = Mockito.mock(LovrabetSDKClient.class);
// 设置 Mock 行为
LovrabetResult<String> mockResult = new LovrabetResult<>();
mockResult.setSuccess(true);
mockResult.setData("12345");
Mockito.when(mockClient.create(Mockito.any()))
.thenReturn(mockResult);
// 测试
CustomerService service = new CustomerService(mockClient);
String id = service.createCustomer(buildTestCustomer());
// 验证
assertEquals("12345", id);
Mockito.verify(mockClient, Mockito.times(1)).create(Mockito.any());
}
}
6.2 集成测试
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class DataServiceIntegrationTest {
@Autowired
private DataService dataService;
@Test
public void testGetCustomers() {
List<Map<String, Object>> customers = dataService.getCustomers(1, 10);
assertNotNull(customers);
assertTrue(customers.size() > 0);
}
@Test
public void testCreateAndQuery() {
// 创建测试数据
Map<String, Object> customerData = buildTestData();
String id = dataService.createCustomer(customerData);
assertNotNull(id);
// 查询验证
Map<String, Object> retrieved = dataService.getCustomer(id);
assertEquals(customerData.get("TEXT_1"), retrieved.get("TEXT_1"));
}
}
6.3 测试数据隔离
/**
* 测试数据管理
*/
public class TestDataManager {
private List<String> createdIds = new ArrayList<>();
/**
* 创建测试数据
*/
public String createTestData(LovrabetSDKClient sdkClient) {
LovrabetRequest request = new LovrabetRequest();
// 设置测试数据标识
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("TEXT_1", "TEST_" + System.currentTimeMillis());
request.setParamMap(paramMap);
LovrabetResult<String> result = sdkClient.create(request);
if (result.isSuccess()) {
String id = result.getData();
createdIds.add(id); // 记录创建的 ID
return id;
}
return null;
}
/**
* 清理测试数据
*/
public void cleanup(LovrabetSDKClient sdkClient) {
for (String id : createdIds) {
// 清理测试数据的逻辑
logger.debug("清理测试数据: {}", id);
}
createdIds.clear();
}
}
7. 部署建议
7.1 环境配置
使用不同的配置文件管理不同环境:
# application-dev.yml (开发环境)
lovrabet:
access-key: ${LOVRABET_ACCESS_KEY}
base-url: https://runtime.lovrabet.com/openapi
app-code: app-dev-xxx
---
# application-prod.yml (生产环境)
lovrabet:
access-key: ${LOVRABET_ACCESS_KEY}
base-url: https://runtime.lovrabet.com/openapi
app-code: app-prod-xxx
7.2 健康检查
实现健康检查端点:
@RestController
public class HealthController {
private final LovrabetSDKClient sdkClient;
@GetMapping("/health/lovrabet")
public Map<String, Object> checkLovrabetHealth() {
Map<String, Object> health = new HashMap<>();
try {
// 尝试调用一个简单的 API
LovrabetRequest request = new LovrabetRequest();
request.setAppCode(appCode);
long startTime = System.currentTimeMillis();
LovrabetResult<?> result = sdkClient.getDatasetCodeList(request);
long duration = System.currentTimeMillis() - startTime;
health.put("status", result.isSuccess() ? "UP" : "DOWN");
health.put("responseTime", duration + "ms");
health.put("message", result.getResultMsg());
} catch (Exception e) {
health.put("status", "DOWN");
health.put("error", e.getMessage());
}
return health;
}
}
7.3 监控指标
使用 Micrometer 收集监控指标:
import io.micrometer.core.instrument.*;
@Service
public class MonitoredDataService {
private final LovrabetSDKClient sdkClient;
private final MeterRegistry meterRegistry;
private final Counter successCounter;
private final Counter failureCounter;
private final Timer responseTimer;
public MonitoredDataService(
LovrabetSDKClient sdkClient,
MeterRegistry meterRegistry) {
this.sdkClient = sdkClient;
this.meterRegistry = meterRegistry;
// 注册指标
this.successCounter = Counter.builder("lovrabet.api.success")
.description("API 调用成功次数")
.register(meterRegistry);
this.failureCounter = Counter.builder("lovrabet.api.failure")
.description("API 调用失败次数")
.register(meterRegistry);
this.responseTimer = Timer.builder("lovrabet.api.response.time")
.description("API 响应时间")
.register(meterRegistry);
}
public Map<String, Object> getData(String id) {
return responseTimer.record(() -> {
LovrabetResult<?> result = sdkClient.getOne(buildRequest(id));
if (result.isSuccess()) {
successCounter.increment();
return (Map<String, Object>) result.getData();
} else {
failureCounter.increment();
return null;
}
});
}
}
7.4 容器化部署
Dockerfile 示例:
FROM openjdk:11-jre-slim
# 设置工作目录
WORKDIR /app
# 复制 jar 文件
COPY target/myapp.jar app.jar
# 设置环境变量(生产环境应通过 Secret 管理)
ENV LOVRABET_ACCESS_KEY=""
# 暴露端口
EXPOSE 8080
# 启动应用
ENTRYPOINT ["java", "-jar", "app.jar"]
Kubernetes 部署配置:
apiVersion: v1
kind: Secret
metadata:
name: lovrabet-secret
type: Opaque
data:
access-key: <base64-encoded-access-key>
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:latest
env:
- name: LOVRABET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: lovrabet-secret
key: access-key
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/lovrabet
port: 8080
initialDelaySeconds: 20
periodSeconds: 5
总结
遵循以上最佳实践,您可以构建高性能、高可用、安全的生产级应用:
- 客户端管理 - 使用单例模式复用客户端
- 安全性 - 妥善管理 AccessKey,避免硬编码
- 性能优化 - 合理分页、使用缓存、控制并发
- 错误处理 - 统一错误处理、实现重试机制
- 日志管理 - 结构化日志、敏感信息脱敏
- 测试策略 - 完善的单元测试和集成测试
- 部署建议 - 健康检查、监控指标、容器化部署
下一步
掌握最佳实践后,建议按以下顺序继续学习:
-
📖 快速开始 5 分钟完成第一个 CRUD 程序
-
📚 核心概念 深入理解 SDK 工作原理
-
📋 API 参考 查阅完整的接口文档
-
💡 业务示例 学习 5 个真实场景的实现
-
🚀 最佳实践 ← 当前位置 掌握生产环境优化技巧
-
❓ 常见问题 ← 推荐下一步 解决使用中的疑问
需要帮助? 访问 常见问题 或联系 service@lovrabet.com