package com.adx.tencent.honor; import com.adx.tencent.baidu.model.PaymentInfo; import com.adx.tencent.honor.model.HonorBidRecord; import com.adx.tencent.honor.model.HonorMediaCallbackRecord; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URI; import java.net.URLEncoder; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Instant; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; public class HonorClient { private static final Logger log = LoggerFactory.getLogger(HonorClient.class); private static final String FIXED_CHANNEL_ID = "9"; private final String baseUrl; private final ObjectMapper objectMapper; private final HttpClient httpClient; public HonorClient(String baseUrl, String channelId, ObjectMapper objectMapper) { this.baseUrl = (baseUrl == null || baseUrl.isBlank()) ? "https://ads-drcn.platform.hihonorcloud.com" : baseUrl.replaceAll("/+$", ""); this.objectMapper = objectMapper; this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build(); } public HonorMediaCallbackRecord sendConversion(HonorBidRecord bid, PaymentInfo payment, int conversionId) { HonorMediaCallbackRecord record = new HonorMediaCallbackRecord(); record.setQk(payment.getQk()); record.setTagId(bid.getTagId()); record.setEventType(payment.getAct()); record.setPurchase(payment.getPayment()); record.setAttempt(1); record.setCreatedAt(Instant.now()); Map params = buildParams(bid, payment, conversionId); String callbackUrl = baseUrl + "/api/ad-tracking/v1/conversion?" + toQuery(params); record.setCallbackUrl(callbackUrl); record.setRequestBody(toJson(params)); record.setEventTimeMs(Long.parseLong(params.get("conversionTime"))); try { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(callbackUrl)) .GET() .timeout(Duration.ofSeconds(15)) .build(); HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray()); String body = new String(response.body(), StandardCharsets.UTF_8); record.setStatus(response.statusCode()); record.setResponseBody(body); record.setOk(response.statusCode() >= 200 && response.statusCode() < 300); if (!record.isOk()) { record.setErrorMessage("HTTP status: " + response.statusCode()); } log.info("[Honor] callback结果: qk={}, tagId={}, conversionId={}, status={}, ok={}, response={}", payment.getQk(), bid.getTagId(), conversionId, response.statusCode(), record.isOk(), body); } catch (Exception e) { record.setOk(false); record.setErrorMessage(e.getMessage()); log.warn("[Honor] callback失败: qk={}, tagId={}, conversionId={}, error={}", payment.getQk(), bid.getTagId(), conversionId, e.getMessage()); } return record; } public HonorMediaCallbackRecord retryCallback(HonorMediaCallbackRecord original) throws Exception { HonorMediaCallbackRecord retry = copy(original); retry.setAttempt(original.getAttempt() + 1); retry.setCreatedAt(Instant.now()); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(original.getCallbackUrl())) .GET() .timeout(Duration.ofSeconds(15)) .build(); HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray()); retry.setStatus(response.statusCode()); retry.setResponseBody(new String(response.body(), StandardCharsets.UTF_8)); retry.setOk(response.statusCode() >= 200 && response.statusCode() < 300); if (!retry.isOk()) retry.setErrorMessage("HTTP status: " + response.statusCode()); return retry; } private Map buildParams(HonorBidRecord bid, PaymentInfo payment, int conversionId) { Map mediaParams = bid.getMediaParams(); long monitorTime = parseLong(mediaParams, "eventTime"); long conversionTime = Math.max(Instant.now().toEpochMilli(), monitorTime + 1); Map params = new LinkedHashMap<>(); put(params, "trackId", value(mediaParams, "trackId")); params.put("conversionId", String.valueOf(conversionId)); params.put("conversionTime", String.valueOf(conversionTime)); put(params, "advertiserId", value(mediaParams, "advertiserId")); put(params, "oaid", value(mediaParams, "oaid")); params.put("id_type", "0"); params.put("channelId", FIXED_CHANNEL_ID); put(params, "pkgName", firstNonBlank(value(mediaParams, "pkgName"), bid.getPackageName())); put(params, "extra", urlEncode("{\"tagId\":\"" + bid.getTagId() + "\"}")); if (conversionId == 10004) { params.put("payAmount", String.format(Locale.ROOT, "%.2f", payment.getGmv() > 0 ? payment.getGmv() : payment.getPayment())); put(params, "orderId", buildOrderId(payment)); } return params; } private static HonorMediaCallbackRecord copy(HonorMediaCallbackRecord src) { HonorMediaCallbackRecord copy = new HonorMediaCallbackRecord(); copy.setDedupeKey(src.getDedupeKey()); copy.setQk(src.getQk()); copy.setTagId(src.getTagId()); copy.setCallbackUrl(src.getCallbackUrl()); copy.setEventType(src.getEventType()); copy.setEventTimeMs(src.getEventTimeMs()); copy.setPurchase(src.getPurchase()); copy.setStatus(src.getStatus()); copy.setOk(src.isOk()); copy.setAttempt(src.getAttempt()); copy.setResponseBody(src.getResponseBody()); copy.setErrorMessage(src.getErrorMessage()); copy.setRequestBody(src.getRequestBody()); copy.setCreatedAt(src.getCreatedAt()); return copy; } private String toJson(Object value) { try { return objectMapper.writeValueAsString(value); } catch (Exception e) { return String.valueOf(value); } } private static String toQuery(Map params) { StringBuilder sb = new StringBuilder(); boolean first = true; for (Map.Entry entry : params.entrySet()) { if (entry.getValue() == null || entry.getValue().isBlank()) continue; if (!first) sb.append('&'); first = false; sb.append(urlEncode(entry.getKey())).append('=').append(urlEncode(entry.getValue())); } return sb.toString(); } private static String urlEncode(String value) { return URLEncoder.encode(value, StandardCharsets.UTF_8); } private static void put(Map params, String key, String value) { if (value != null && !value.isBlank()) params.put(key, value); } private static String value(Map params, String key) { return params != null ? params.get(key) : null; } private static long parseLong(Map params, String key) { try { return Long.parseLong(value(params, key)); } catch (Exception e) { return 0L; } } private static String firstNonBlank(String... values) { for (String value : values) { if (value != null && !value.isBlank()) return value; } return null; } private static String buildOrderId(PaymentInfo payment) { String raw = (payment.getTu() != null ? payment.getTu() : "") + "_" + (payment.getQk() != null ? payment.getQk() : "") + "_" + payment.getAct(); return raw.length() <= 64 ? raw : raw.substring(0, 64); } }