121 lines
4.4 KiB
Java
121 lines
4.4 KiB
Java
package com.mosquito.project.job;
|
||
|
||
import org.slf4j.Logger;
|
||
import org.slf4j.LoggerFactory;
|
||
import org.springframework.beans.factory.annotation.Value;
|
||
import org.springframework.http.*;
|
||
import org.springframework.stereotype.Component;
|
||
import org.springframework.web.client.RestTemplate;
|
||
|
||
import java.util.UUID;
|
||
|
||
/**
|
||
* 内部奖励发放适配器
|
||
* 对接内部账户系统API,支持超时、重试、幂等键
|
||
*/
|
||
@Component
|
||
public class InternalRewardDistributor implements RewardJobProcessor.RewardDistributor {
|
||
|
||
private static final Logger log = LoggerFactory.getLogger(InternalRewardDistributor.class);
|
||
|
||
@Value("${app.internal-account.api-url:}")
|
||
private String apiUrl;
|
||
|
||
@Value("${app.internal-account.api-key:}")
|
||
private String apiKey;
|
||
|
||
@Value("${app.internal-account.timeout:5000}")
|
||
private int timeout;
|
||
|
||
/**
|
||
* 是否允许模拟成功模式(仅开发/测试环境使用)
|
||
* 生产环境必须设置为false,否则可能造成"假成功发放"
|
||
*/
|
||
@Value("${app.internal-account.allow-mock-success:false}")
|
||
private boolean allowMockSuccess;
|
||
|
||
private final RestTemplate restTemplate;
|
||
|
||
public InternalRewardDistributor(RestTemplate restTemplate) {
|
||
this.restTemplate = restTemplate;
|
||
}
|
||
|
||
@Override
|
||
public boolean distribute(Long userId, Long activityId, String trackingId, int points, String rewardType) {
|
||
// 生成幂等键,确保重试不会重复发放
|
||
String idempotencyKey = generateIdempotencyKey(userId, activityId, trackingId);
|
||
|
||
log.info("发放奖励: userId={}, activityId={}, trackingId={}, points={}, type={}, idempotencyKey={}, allowMockSuccess={}",
|
||
userId, activityId, trackingId, points, rewardType, idempotencyKey, allowMockSuccess);
|
||
|
||
// 如果未配置API地址
|
||
if (apiUrl == null || apiUrl.isBlank()) {
|
||
// 生产环境(Fail-Closed):不允许模拟成功,返回false进入重试队列
|
||
if (!allowMockSuccess) {
|
||
log.error("内部账户系统API地址未配置且不允许模拟成功,奖励发放失败,请检查配置");
|
||
return false;
|
||
}
|
||
// 开发/测试环境:允许模拟成功
|
||
log.warn("内部账户系统API地址未配置,模拟奖励发放成功(仅开发环境)");
|
||
return true;
|
||
}
|
||
|
||
try {
|
||
return callInternalAccountApi(userId, points, rewardType, idempotencyKey);
|
||
} catch (Exception e) {
|
||
log.error("奖励发放失败: userId={}, error={}", userId, e.getMessage());
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 调用内部账户系统API
|
||
*/
|
||
private boolean callInternalAccountApi(Long userId, int points, String rewardType, String idempotencyKey) {
|
||
// 构建请求体
|
||
String requestBody = String.format(
|
||
"{\"userId\":%d,\"points\":%d,\"type\":\"%s\",\"idempotencyKey\":\"%s\"}",
|
||
userId, points, rewardType, idempotencyKey
|
||
);
|
||
|
||
// 设置请求头
|
||
HttpHeaders headers = new HttpHeaders();
|
||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||
if (apiKey != null && !apiKey.isBlank()) {
|
||
headers.set("X-API-Key", apiKey);
|
||
}
|
||
headers.set("X-Idempotency-Key", idempotencyKey);
|
||
|
||
HttpEntity<String> request = new HttpEntity<>(requestBody, headers);
|
||
|
||
try {
|
||
ResponseEntity<String> response = restTemplate.exchange(
|
||
apiUrl + "/api/v1/points/grant",
|
||
HttpMethod.POST,
|
||
request,
|
||
String.class
|
||
);
|
||
|
||
if (response.getStatusCode() == HttpStatus.OK || response.getStatusCode() == HttpStatus.CREATED) {
|
||
log.info("奖励发放成功: userId={}, response={}", userId, response.getBody());
|
||
return true;
|
||
} else {
|
||
log.error("奖励发放失败: userId={}, status={}", userId, response.getStatusCode());
|
||
return false;
|
||
}
|
||
} catch (Exception e) {
|
||
log.error("调用内部账户系统API异常: userId={}, error={}", userId, e.getMessage());
|
||
throw e;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 生成幂等键
|
||
*/
|
||
private String generateIdempotencyKey(Long userId, Long activityId, String trackingId) {
|
||
return UUID.nameUUIDFromBytes(
|
||
String.format("%d-%d-%s", userId, activityId, trackingId).getBytes()
|
||
).toString();
|
||
}
|
||
}
|