chore: sync local latest state and repository cleanup
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user