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 request = new HttpEntity<>(requestBody, headers); try { ResponseEntity 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(); } }