| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- package com.adx.tencent.oppo.controller;
- import com.adx.tencent.baidu.AdxClient;
- import com.adx.tencent.baidu.model.NormalizedBidResponse;
- import com.adx.tencent.baidu.model.TrackingResult;
- import com.adx.tencent.config.AppProperties;
- import com.adx.tencent.oppo.model.OppoBidRecord;
- import com.adx.tencent.oppo.model.OppoTrackingRecord;
- import com.adx.tencent.oppo.store.OppoHotStore;
- import com.adx.tencent.rta.BaiduRtaDecision;
- import com.adx.tencent.rta.BaiduRtaService;
- import jakarta.servlet.http.HttpServletRequest;
- import jakarta.servlet.http.HttpServletResponse;
- import org.springframework.http.HttpStatus;
- import org.springframework.lang.Nullable;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RestController;
- import org.springframework.web.server.ResponseStatusException;
- import java.time.Instant;
- import java.util.*;
- import java.util.concurrent.Semaphore;
- /**
- * OPPO 监测流量入口:OPPO -> 本服务 -> 百度 ADX。
- */
- @RestController
- public class OppoTrackingController {
- private final AdxClient adx;
- private final OppoHotStore store;
- private final AppProperties props;
- private final BaiduRtaService rtaService;
- private final Semaphore impression = new Semaphore(50), click = new Semaphore(50);
- public OppoTrackingController(AdxClient adx, @Nullable OppoHotStore store, AppProperties props,
- @Nullable BaiduRtaService rtaService) {
- this.adx = adx;
- this.store = store;
- this.props = props;
- this.rtaService = rtaService;
- }
- @GetMapping("/oppo/impression")
- public Object impression(HttpServletRequest request) throws Exception {
- impression.acquire();
- try {
- return handleImpression(request);
- } finally {
- impression.release();
- }
- }
- @GetMapping("/oppo/click")
- public Object click(HttpServletRequest request, HttpServletResponse response) throws Exception {
- click.acquire();
- try {
- return handleClick(request, response);
- } finally {
- click.release();
- }
- }
- private Object handleImpression(HttpServletRequest request) throws Exception {
- Map<String, String> p = params(request);
- String trace = required(p, "req", "requestId");
- ensureReady();
- applyResolvedDefaults(p);
- BaiduRtaDecision rtaDecision = evaluateRta(trace, p, request);
- Map<String, String> rtaParams = enrichMediaParams(p, rtaDecision);
- if (!rtaDecision.allowsBid()) {
- OppoBidRecord blocked = blockedBidRecord(trace, "impression", rtaParams, rtaDecision);
- saveBlockedBid(blocked);
- return responseBody(rtaParams, blocked.getQk(), trace, blocked.getTagId(), rtaDecision);
- }
- NormalizedBidResponse response = adx.requestBid("oppo", trace, buildBidRequest(request, trace, rtaParams, rtaDecision));
- if (response == null) throw bad("empty baidu response");
- OppoBidRecord record = record(response, trace, "impression", rtaParams);
- record.setRtaCode(rtaDecision.getCode());
- store.recordBid(record);
- report(record, response.getBid() == null ? null : response.getBid().getShowUrls(), "impression");
- return responseBody(rtaParams, record.getQk(), trace, record.getTagId(), rtaDecision);
- }
- private Object handleClick(HttpServletRequest request, HttpServletResponse response) throws Exception {
- Map<String, String> p = params(request);
- String trace = required(p, "req", "requestId");
- ensureReady();
- OppoBidRecord record = store.findBidByMediaTrace(trace);
- BaiduRtaDecision rtaDecision = null;
- if (record == null) {
- applyResolvedDefaults(p);
- rtaDecision = evaluateRta(trace, p, request);
- Map<String, String> rtaParams = enrichMediaParams(p, rtaDecision);
- if (!rtaDecision.allowsBid()) {
- OppoBidRecord blocked = blockedBidRecord(trace, "click", rtaParams, rtaDecision);
- saveBlockedBid(blocked);
- return responseBody(rtaParams, blocked.getQk(), trace, blocked.getTagId(), rtaDecision);
- }
- NormalizedBidResponse bid = adx.requestBid("oppo", trace, buildBidRequest(request, trace, rtaParams, rtaDecision));
- if (bid == null) throw bad("empty baidu response");
- record = record(bid, trace, "click", rtaParams);
- record.setRtaCode(rtaDecision.getCode());
- store.recordBid(record);
- report(record, bid.getBid() == null ? null : bid.getBid().getShowUrls(), "impression");
- }
- report(record, record.getClickUrls(), "click");
- try {
- store.shrinkAfterClick(record);
- } catch (Exception ignored) {
- // 与其他渠道一致,缓存瘦身失败不阻断已完成上报的点击跳转。
- }
- String destination = record.getAppStoreLink();
- if (destination == null || destination.isBlank()) destination = record.getLandingPage();
- if (destination != null && !destination.isBlank()) {
- response.sendRedirect(destination);
- return null;
- }
- return responseBody(p, record.getQk(), trace, record.getTagId(), rtaDecision);
- }
- private void report(OppoBidRecord r, List<String> urls, String kind) {
- if (urls == null) return;
- try {
- List<TrackingResult> results = "impression".equals(kind) ? adx.reportImpression(urls, r.getPrice() == null ? 0 : r.getPrice()) : adx.reportClick(urls);
- for (TrackingResult x : results) {
- OppoTrackingRecord t = new OppoTrackingRecord();
- t.setQk(r.getQk());
- t.setTagId(r.getTagId());
- t.setKind(kind);
- t.setUrl(x.getUrl());
- t.setStatus(x.getStatus());
- t.setOk(x.isOk());
- t.setCreatedAt(Instant.now());
- if (store != null) store.recordTracking(t);
- }
- } catch (Exception ignored) {
- }
- }
- private OppoBidRecord record(NormalizedBidResponse response, String trace, String kind, Map<String, String> p) {
- OppoBidRecord r = new OppoBidRecord();
- r.setQk(response.getQk());
- r.setMediaTraceId(trace);
- r.setPlatform(resolvePlatform(p));
- r.setAccountId(first(p, "ownerId", "owner_id"));
- r.setTagId(first(p, "tagId", "tag_id", "baidu_tag_id"));
- r.setEventType(kind);
- r.setMediaParams(p);
- r.setCreatedAt(Instant.now());
- if (response.getBid() != null) {
- r.setPrice(response.getBid().getPrice());
- r.setShowUrls(response.getBid().getShowUrls());
- r.setClickUrls(response.getBid().getClickUrls());
- r.setLandingPage(response.getBid().getLandingPage());
- r.setAppStoreLink(response.getBid().getAppStoreLink());
- r.setTagId(first(response.getBid().getTagId(), r.getTagId()));
- if (response.getBid().getAdm() != null) r.setPackageName(response.getBid().getAdm().getPackageName());
- }
- r.setPackageName(firstValue(r.getPackageName(), p.get("pkg"), p.get("packageName")));
- return r;
- }
- private BaiduRtaDecision evaluateRta(String trace, Map<String, String> p, HttpServletRequest request) {
- if (rtaService == null) return BaiduRtaDecision.bypass();
- return rtaService.evaluate(
- "oppo",
- resolveBaiduAppId(p),
- resolveTagId(p),
- resolvePlatform(p),
- p,
- firstValue(first(p, "ip", "clientIp", "client_ip"), clientIp(request, p)),
- normalizeUserAgent(first(p, "ua", "user_agent", "useragent"), resolvePlatform(p)));
- }
- private OppoBidRecord blockedBidRecord(String trace, String kind, Map<String, String> p, BaiduRtaDecision decision) {
- OppoBidRecord r = new OppoBidRecord();
- r.setQk(firstValue(decision.getQk(), decision.getRequestId(), "oppo-rta-blocked-" + UUID.randomUUID().toString().replace("-", "")));
- r.setMediaTraceId(trace);
- r.setPlatform(resolvePlatform(p));
- r.setAccountId(first(p, "ownerId", "owner_id"));
- r.setTagId(value(resolveTagId(p)));
- r.setEventType(kind);
- r.setRtaCode(decision.getCode());
- r.setPrice(0L);
- r.setPackageName(first(p, "pkg", "packageName"));
- r.setMediaParams(enrichMediaParams(p, decision));
- r.setCreatedAt(Instant.now());
- return r;
- }
- private static Map<String, String> enrichMediaParams(Map<String, String> params, BaiduRtaDecision decision) {
- Map<String, String> enriched = new LinkedHashMap<>();
- if (params != null) enriched.putAll(params);
- if (decision == null || !decision.isApplied()) return enriched;
- putIfNotBlank(enriched, "rta_tu", decision.getTu());
- putIfNotBlank(enriched, "rta_order_id", firstValue(decision.getOrderIds(), decision.getRequestOrderIds()));
- putIfNotBlank(enriched, "rta_request_order_id", decision.getRequestOrderIds());
- putIfNotBlank(enriched, "rta_qk", decision.getQk());
- putIfNotBlank(enriched, "rta_request_id", decision.getRequestId());
- if (decision.getCode() != null) enriched.put("rta_code", String.valueOf(decision.getCode()));
- return enriched;
- }
- private void saveBlockedBid(OppoBidRecord record) {
- store.recordBlockedBid(record);
- }
- private static Map<String, Object> responseBody(Map<String, String> params,
- String qk,
- String trace,
- String tagId,
- BaiduRtaDecision decision) {
- Map<String, Object> body = new LinkedHashMap<>();
- body.put("qk", value(qk));
- body.put("req", trace);
- body.put("tagId", value(tagId));
- if ("true".equalsIgnoreCase(firstValue(params.get("_debugRta"), params.get("debugRta")))) {
- body.put("rtaApplied", decision != null && decision.isApplied());
- body.put("rtaMatched", decision == null ? null : decision.isMatched());
- body.put("rtaCode", decision == null ? null : decision.getCode());
- body.put("rtaQk", decision == null ? null : decision.getQk());
- body.put("rtaRequestId", decision == null ? null : decision.getRequestId());
- body.put("rtaRequestOrderId", decision == null ? null : decision.getRequestOrderIds());
- body.put("rtaOrderId", decision == null ? null : decision.getOrderIds());
- body.put("rtaError", decision == null ? null : decision.getErrorMessage());
- }
- return body;
- }
- private Map<String, Object> buildBidRequest(HttpServletRequest req,
- String trace,
- Map<String, String> p,
- BaiduRtaDecision rtaDecision) {
- String app = resolveBaiduAppId(p);
- String tag = resolveTagId(p);
- Map<String, Object> imp = new LinkedHashMap<>();
- imp.put("id", "1");
- imp.put("appId", app);
- imp.put("tagId", tag);
- imp.put("secure", 1);
- imp.put("adType", 0);
- imp.put("maxCount", 1);
- imp.put("assets", List.of(Map.of("type", 1)));
- addRtaExtTransData(imp, rtaDecision);
- Map<String, Object> device = new LinkedHashMap<>();
- device.put("ua", firstValue(first(p, "ua", "user_agent"), req.getHeader("User-Agent")));
- device.put("deviceType", 1);
- device.put("ip", clientIp(req, p));
- if (first(p, "oaid") != null) device.put("uid", Map.of("oaid", first(p, "oaid")));
- Map<String, Object> body = new LinkedHashMap<>();
- body.put("mediaId", props.getBaiduMediaId());
- body.put("reqId", trace);
- body.put("imp", List.of(imp));
- body.put("device", device);
- return body;
- }
- private static void addRtaExtTransData(Map<String, Object> imp, BaiduRtaDecision decision) {
- String orderId = rtaOrderId(decision);
- if (orderId == null) {
- return;
- }
- Map<String, Object> extTransData = new LinkedHashMap<>();
- extTransData.put("key", "order_id");
- extTransData.put("value", orderId);
- imp.put("ext_trans_data", List.of(extTransData));
- }
- private static String rtaOrderId(BaiduRtaDecision decision) {
- if (decision == null || !decision.isApplied() || !decision.isMatched()) {
- return null;
- }
- return firstValue(decision.getOrderIds(), decision.getRequestOrderIds());
- }
- private static String clientIp(HttpServletRequest request, Map<String, String> params) {
- String parameterIp = first(params, "ip", "clientIp", "client_ip");
- if (parameterIp != null) return parameterIp;
- String forwarded = request.getHeader("X-Forwarded-For");
- if (forwarded != null && !forwarded.isBlank()) return forwarded.split(",", 2)[0].trim();
- String real = request.getHeader("X-Real-IP");
- return real == null || real.isBlank() ? request.getRemoteAddr() : real.trim();
- }
- private Map<String, String> params(HttpServletRequest r) {
- Map<String, String> p = new LinkedHashMap<>();
- r.getParameterMap().forEach((k, v) -> {
- if (v != null && v.length > 0 && v[0] != null) p.put(k, v[0].trim());
- });
- return p;
- }
- private void applyResolvedDefaults(Map<String, String> p) {
- p.put("platform", resolvePlatform(p));
- String tag = resolveTagId(p);
- if (tag != null) p.put("tagId", tag);
- String app = resolveBaiduAppId(p);
- if (app != null) p.put("baidu_app_id", app);
- }
- private String resolveBaiduAppId(Map<String, String> p) {
- return firstValue(first(p, "baidu_app_id"), props.getBaiduAppId());
- }
- private String resolveTagId(Map<String, String> p) {
- return firstValue(first(p, "tagId", "tag_id", "baidu_tag_id"), props.getBaiduTagId());
- }
- private static String resolvePlatform(Map<String, String> p) {
- String platform = first(p, "platform", "os");
- if (platform == null) return "android";
- String normalized = platform.trim().toLowerCase(Locale.ROOT);
- if ("ios".equals(normalized)) return "ios";
- if ("harmony".equals(normalized) || "hongmeng".equals(normalized)) return "harmony";
- return "android";
- }
- private void ensureReady() {
- if (store == null)
- throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "oppo store not configured");
- }
- private static String required(Map<String, String> p, String... keys) {
- String v = first(p, keys);
- if (v == null || v.isBlank())
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "missing OPPO request id");
- return v;
- }
- private static String first(Map<String, String> p, String... k) {
- for (String x : k) {
- String v = p.get(x);
- if (v != null && !v.isBlank()) return v;
- }
- return null;
- }
- private static String first(String a, String b) {
- return a != null && !a.isBlank() ? a : b;
- }
- private static String firstValue(String... values) {
- for (String v : values) if (v != null && !v.isBlank()) return v;
- return null;
- }
- private static void putIfNotBlank(Map<String, String> params, String key, String value) {
- if (value != null && !value.isBlank()) params.put(key, value);
- }
- private static String normalizeUserAgent(String ua, String platform) {
- if (ua != null && !ua.isBlank()) return ua.trim();
- if ("ios".equals(platform)) {
- return "Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1";
- }
- return "Mozilla/5.0 (Linux; Android 14; Pixel 8 Build/UQ1A.240205.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.71 Mobile Safari/537.36";
- }
- private static String value(String v) {
- return v == null ? "" : v;
- }
- private static ResponseStatusException bad(String s) {
- return new ResponseStatusException(HttpStatus.BAD_GATEWAY, s);
- }
- }
|