ManualTencentCallbackService.java 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. package com.adx.tencent.conversionsync;
  2. import com.adx.tencent.baidu.model.PaymentInfo;
  3. import com.adx.tencent.storage.RedisHotStore;
  4. import com.adx.tencent.storage.TiDBColdStore;
  5. import com.adx.tencent.storage.model.BidRecord;
  6. import com.adx.tencent.storage.model.MediaCallbackRecord;
  7. import com.adx.tencent.tencent.TencentClient;
  8. import org.slf4j.Logger;
  9. import org.slf4j.LoggerFactory;
  10. import org.springframework.web.server.ResponseStatusException;
  11. import java.time.Instant;
  12. import java.util.LinkedHashMap;
  13. import java.util.LinkedHashSet;
  14. import java.util.Map;
  15. import java.util.Set;
  16. import static org.springframework.http.HttpStatus.BAD_REQUEST;
  17. import static org.springframework.http.HttpStatus.NOT_FOUND;
  18. public class ManualTencentCallbackService {
  19. private static final Logger log = LoggerFactory.getLogger(ManualTencentCallbackService.class);
  20. private final TiDBColdStore coldStore;
  21. private final RedisHotStore hotStore;
  22. private final TencentClient tencentClient;
  23. public ManualTencentCallbackService(TiDBColdStore coldStore,
  24. RedisHotStore hotStore,
  25. TencentClient tencentClient) {
  26. this.coldStore = coldStore;
  27. this.hotStore = hotStore;
  28. this.tencentClient = tencentClient;
  29. }
  30. public Map<String, Object> replayDeductedCallback(long id) {
  31. MediaCallbackRecord original = coldStore.getMediaCallbackById(id);
  32. if (original == null) {
  33. throw new ResponseStatusException(NOT_FOUND, "callback record not found");
  34. }
  35. if (!"tencent".equals(original.getMedia())) {
  36. throw new ResponseStatusException(BAD_REQUEST, "callback record is not tencent");
  37. }
  38. if (!MediaCallbackRecord.DISPATCH_STATUS_DEDUCTED.equals(original.getDispatchStatus())) {
  39. throw new ResponseStatusException(BAD_REQUEST, "callback record is not deducted");
  40. }
  41. MediaCallbackRecord replay = replayOnce(original);
  42. replay.setId(original.getId());
  43. coldStore.updateMediaCallback(replay);
  44. Map<String, Object> result = new LinkedHashMap<>();
  45. result.put("id", id);
  46. result.put("dedupeKey", replay.getDedupeKey());
  47. result.put("before", snapshot(original));
  48. result.put("after", snapshot(replay));
  49. return result;
  50. }
  51. private MediaCallbackRecord replayOnce(MediaCallbackRecord original) {
  52. if (original.getRequestBody() != null && !original.getRequestBody().isBlank()) {
  53. try {
  54. MediaCallbackRecord replay = tencentClient.retryCallback(original);
  55. replay.setDedupeKey(original.getDedupeKey());
  56. replay.setAttempt(Math.max(original.getAttempt(), 1) + 1);
  57. if (replay.getTrackingVersion() == null || replay.getTrackingVersion().isBlank()) {
  58. replay.setTrackingVersion(original.getTrackingVersion());
  59. }
  60. return replay;
  61. } catch (Exception e) {
  62. MediaCallbackRecord failed = copyForFailure(original);
  63. failed.setErrorMessage(e.getMessage());
  64. return failed;
  65. }
  66. }
  67. BidRecord bid = hotStore.findBidByQk(original.getQk());
  68. if (bid == null) {
  69. bid = coldStore.getBidByQk(original.getQk());
  70. }
  71. if (bid == null) {
  72. throw new ResponseStatusException(NOT_FOUND, "bid not found for qk=" + original.getQk());
  73. }
  74. DedupeInfo dedupeInfo = parseDedupeInfo(original);
  75. String actionType = dedupeInfo.actionType != null && !dedupeInfo.actionType.isBlank()
  76. ? dedupeInfo.actionType
  77. : tencentClient.conversionActionType(original.getEventType());
  78. patchBidForReplay(bid, original, dedupeInfo);
  79. PaymentInfo payment = new PaymentInfo();
  80. payment.setQk(original.getQk());
  81. payment.setDate(dedupeInfo.date);
  82. payment.setDeviceId(dedupeInfo.deviceId);
  83. payment.setAct(original.getEventType());
  84. payment.setPayment(original.getPurchase());
  85. payment.setGmv(original.getPurchase());
  86. MediaCallbackRecord replay = tencentClient.sendConversionEvent(
  87. bid,
  88. payment,
  89. resolvePlatform(bid),
  90. actionType
  91. );
  92. replay.setDedupeKey(original.getDedupeKey());
  93. replay.setAttempt(Math.max(original.getAttempt(), 1) + 1);
  94. if (replay.getTrackingVersion() == null || replay.getTrackingVersion().isBlank()) {
  95. replay.setTrackingVersion(original.getTrackingVersion());
  96. }
  97. replay.setDispatchStatus(MediaCallbackRecord.DISPATCH_STATUS_SENT);
  98. replay.setCreatedAt(Instant.now());
  99. log.info("[ManualTencentCallback] manual replay sent | qk={} | dedupeKey={} | ok={} | status={}",
  100. original.getQk(), original.getDedupeKey(), replay.isOk(), replay.getStatus());
  101. return replay;
  102. }
  103. private void patchBidForReplay(BidRecord bid, MediaCallbackRecord original, DedupeInfo dedupeInfo) {
  104. if ((bid.getTagId() == null || bid.getTagId().isBlank()) && dedupeInfo.tagId != null && !dedupeInfo.tagId.isBlank()) {
  105. bid.setTagId(dedupeInfo.tagId);
  106. }
  107. Map<String, String> params = bid.getMediaParams();
  108. if (params == null) {
  109. params = new LinkedHashMap<>();
  110. bid.setMediaParams(params);
  111. } else if (!(params instanceof LinkedHashMap<String, String>)) {
  112. params = new LinkedHashMap<>(params);
  113. bid.setMediaParams(params);
  114. }
  115. if ((params.get("callback") == null || params.get("callback").isBlank())
  116. && original.getCallbackUrl() != null && !original.getCallbackUrl().isBlank()) {
  117. params.put("callback", original.getCallbackUrl());
  118. }
  119. }
  120. private static MediaCallbackRecord copyForFailure(MediaCallbackRecord original) {
  121. MediaCallbackRecord failed = new MediaCallbackRecord();
  122. failed.setDedupeKey(original.getDedupeKey());
  123. failed.setMedia(original.getMedia());
  124. failed.setQk(original.getQk());
  125. failed.setCallbackUrl(original.getCallbackUrl());
  126. failed.setRequestBody(original.getRequestBody());
  127. failed.setEventType(original.getEventType());
  128. failed.setEventTimeMs(Instant.now().toEpochMilli());
  129. failed.setPurchase(original.getPurchase());
  130. failed.setStatus(original.getStatus());
  131. failed.setOk(false);
  132. failed.setAttempt(Math.max(original.getAttempt(), 1) + 1);
  133. failed.setResponseBody(original.getResponseBody());
  134. failed.setDispatchStatus(MediaCallbackRecord.DISPATCH_STATUS_SENT);
  135. failed.setTrackingVersion(original.getTrackingVersion());
  136. failed.setCreatedAt(Instant.now());
  137. return failed;
  138. }
  139. private static DedupeInfo parseDedupeInfo(MediaCallbackRecord record) {
  140. DedupeInfo info = new DedupeInfo();
  141. String dedupeKey = record.getDedupeKey();
  142. if (dedupeKey == null || dedupeKey.isBlank()) {
  143. return info;
  144. }
  145. String[] parts = dedupeKey.split(":");
  146. if (parts.length < 7) {
  147. return info;
  148. }
  149. info.date = parts[2];
  150. info.deviceId = parts[3];
  151. info.tagId = parts[4];
  152. info.actionType = joinTail(parts, 6);
  153. return info;
  154. }
  155. private static String joinTail(String[] parts, int start) {
  156. if (parts.length <= start) {
  157. return null;
  158. }
  159. StringBuilder builder = new StringBuilder();
  160. for (int i = start; i < parts.length; i++) {
  161. if (i > start) {
  162. builder.append(':');
  163. }
  164. builder.append(parts[i]);
  165. }
  166. return builder.toString();
  167. }
  168. private static String resolvePlatform(BidRecord bid) {
  169. if (bid.getPlatform() != null && !bid.getPlatform().isBlank()) {
  170. return bid.getPlatform().toLowerCase();
  171. }
  172. if (bid.getMediaParams() == null) {
  173. return "android";
  174. }
  175. String platform = bid.getMediaParams().get("baidu_platform");
  176. if (platform != null && !platform.isBlank()) {
  177. return platform.toLowerCase();
  178. }
  179. String idfa = bid.getMediaParams().get("idfa");
  180. if (idfa != null && !idfa.isBlank()) {
  181. return "ios";
  182. }
  183. return "android";
  184. }
  185. private static Map<String, Object> snapshot(MediaCallbackRecord record) {
  186. Map<String, Object> data = new LinkedHashMap<>();
  187. data.put("id", record.getId());
  188. data.put("dispatchStatus", record.getDispatchStatus());
  189. data.put("attempt", record.getAttempt());
  190. data.put("status", record.getStatus());
  191. data.put("ok", record.isOk());
  192. data.put("errorMessage", record.getErrorMessage());
  193. data.put("callbackUrl", record.getCallbackUrl());
  194. data.put("trackingVersion", record.getTrackingVersion());
  195. return data;
  196. }
  197. private static class DedupeInfo {
  198. private String date;
  199. private String deviceId;
  200. private String tagId;
  201. private String actionType;
  202. }
  203. }