Files
wenzi/src/main/java/com/mosquito/project/job/InternalRewardDistributor.java

121 lines
4.4 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();
}
}