|
@@ -0,0 +1,417 @@
|
|
|
|
|
+package com.fs.app.controller;
|
|
|
|
|
+
|
|
|
|
|
+import cn.hutool.core.util.StrUtil;
|
|
|
|
|
+import com.fs.newAdv.service.IClickTraceService;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
+import org.springframework.web.bind.annotation.GetMapping;
|
|
|
|
|
+import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
|
|
+import org.springframework.web.bind.annotation.RequestParam;
|
|
|
|
|
+import org.springframework.web.bind.annotation.RestController;
|
|
|
|
|
+
|
|
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
|
|
+import java.util.HashMap;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 广告监测控制器(Server-to-Server)
|
|
|
|
|
+ * @author zhangqin
|
|
|
|
|
+ * @date 2025-11-05
|
|
|
|
|
+ */
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+@RestController
|
|
|
|
|
+@RequestMapping("/api/track")
|
|
|
|
|
+public class TrackingController {
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private IClickTraceService clickTraceService;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 百度推广监测端点
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * 监测链接示例:
|
|
|
|
|
+ * https://track.yourdomain.com/api/track/baidu?clkid={clkid}&site_id=100&keyword={keyword}&planid={planid}
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * 百度宏参数说明:
|
|
|
|
|
+ * - {clkid}: 点击ID(必填)
|
|
|
|
|
+ * - {keyword}: 关键词
|
|
|
|
|
+ * - {planid}: 推广计划ID
|
|
|
|
|
+ * - {unitid}: 推广单元ID
|
|
|
|
|
+ * - {creative}: 创意ID
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param allParams 所有URL参数
|
|
|
|
|
+ * @param request HTTP请求
|
|
|
|
|
+ * @param response HTTP响应
|
|
|
|
|
+ */
|
|
|
|
|
+ @GetMapping("/baidu")
|
|
|
|
|
+ public void trackBaidu(
|
|
|
|
|
+ @RequestParam Map<String, String> allParams,
|
|
|
|
|
+ HttpServletRequest request,
|
|
|
|
|
+ HttpServletResponse response) {
|
|
|
|
|
+
|
|
|
|
|
+ log.info("【百度】接收监测请求 | params={}, ip={}",
|
|
|
|
|
+ allParams, getClientIp(request));
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 1. 提取百度特有参数
|
|
|
|
|
+ String clickId = allParams.get("clkid");
|
|
|
|
|
+ if (StrUtil.isBlank(clickId)) {
|
|
|
|
|
+ clickId = allParams.get("logid"); // 百度也可能使用 logid
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (StrUtil.isBlank(clickId)) {
|
|
|
|
|
+ log.warn("【百度】监测请求缺少点击ID | params={}", allParams);
|
|
|
|
|
+ response.setStatus(HttpServletResponse.SC_OK);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 构建标准参数
|
|
|
|
|
+ Map<String, String> standardParams = new HashMap<>(allParams);
|
|
|
|
|
+ standardParams.put("click_id", clickId);
|
|
|
|
|
+ standardParams.put("source", "BAIDU");
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 保存点击追踪记录
|
|
|
|
|
+ Long siteId = extractSiteId(allParams);
|
|
|
|
|
+ clickTraceService.saveClickTrace(
|
|
|
|
|
+ siteId,
|
|
|
|
|
+ standardParams,
|
|
|
|
|
+ null,
|
|
|
|
|
+ getClientIp(request),
|
|
|
|
|
+ request.getHeader("User-Agent")
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ log.info("【百度】监测请求处理成功 | clickId={}, siteId={}", clickId, siteId);
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 返回 200 OK
|
|
|
|
|
+ response.setStatus(HttpServletResponse.SC_OK);
|
|
|
|
|
+
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("【百度】监测请求处理失败 | params={}", allParams, e);
|
|
|
|
|
+ response.setStatus(HttpServletResponse.SC_OK);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 巨量引擎监测端点
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * 监测链接示例:
|
|
|
|
|
+ * https://track.yourdomain.com/api/track/oceanengine?clickid={clickid}&site_id=100&cid={cid}&creativeid={creativeid}
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * 巨量引擎宏参数说明:
|
|
|
|
|
+ * - {clickid}: 点击ID(必填)
|
|
|
|
|
+ * - {cid}: 广告计划ID
|
|
|
|
|
+ * - {creativeid}: 创意ID
|
|
|
|
|
+ * - {aid}: 广告ID
|
|
|
|
|
+ * - {callback_param}: 自定义参数(也可作为点击ID)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param allParams 所有URL参数
|
|
|
|
|
+ * @param request HTTP请求
|
|
|
|
|
+ * @param response HTTP响应
|
|
|
|
|
+ */
|
|
|
|
|
+ @GetMapping("/oceanengine")
|
|
|
|
|
+ public void trackOceanEngine(
|
|
|
|
|
+ @RequestParam Map<String, String> allParams,
|
|
|
|
|
+ HttpServletRequest request,
|
|
|
|
|
+ HttpServletResponse response) {
|
|
|
|
|
+
|
|
|
|
|
+ log.info("【巨量引擎】接收监测请求 | params={}, ip={}",
|
|
|
|
|
+ allParams, getClientIp(request));
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 1. 提取巨量引擎特有参数
|
|
|
|
|
+ String clickId = allParams.get("clickid");
|
|
|
|
|
+ if (StrUtil.isBlank(clickId)) {
|
|
|
|
|
+ clickId = allParams.get("callback_param");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (StrUtil.isBlank(clickId)) {
|
|
|
|
|
+ log.warn("【巨量引擎】监测请求缺少点击ID | params={}", allParams);
|
|
|
|
|
+ response.setStatus(HttpServletResponse.SC_OK);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 构建标准参数
|
|
|
|
|
+ Map<String, String> standardParams = new HashMap<>(allParams);
|
|
|
|
|
+ standardParams.put("click_id", clickId);
|
|
|
|
|
+ standardParams.put("source", "OCEANENGINE");
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 保存点击追踪记录
|
|
|
|
|
+ Long siteId = extractSiteId(allParams);
|
|
|
|
|
+ clickTraceService.saveClickTrace(
|
|
|
|
|
+ siteId,
|
|
|
|
|
+ standardParams,
|
|
|
|
|
+ null,
|
|
|
|
|
+ getClientIp(request),
|
|
|
|
|
+ request.getHeader("User-Agent")
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ log.info("【巨量引擎】监测请求处理成功 | clickId={}, siteId={}", clickId, siteId);
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 返回 200 OK
|
|
|
|
|
+ response.setStatus(HttpServletResponse.SC_OK);
|
|
|
|
|
+
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("【巨量引擎】监测请求处理失败 | params={}", allParams, e);
|
|
|
|
|
+ response.setStatus(HttpServletResponse.SC_OK);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 新浪扶翼监测端点
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * 监测链接示例:
|
|
|
|
|
+ * https://track.yourdomain.com/api/track/sina?click_id={click_id}&site_id=100&campaign_id={campaign_id}
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * 新浪宏参数说明:
|
|
|
|
|
+ * - {click_id}: 点击ID(必填)
|
|
|
|
|
+ * - {campaign_id}: 广告计划ID
|
|
|
|
|
+ * - {ad_id}: 广告ID
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param allParams 所有URL参数
|
|
|
|
|
+ * @param request HTTP请求
|
|
|
|
|
+ * @param response HTTP响应
|
|
|
|
|
+ */
|
|
|
|
|
+ @GetMapping("/sina")
|
|
|
|
|
+ public void trackSina(
|
|
|
|
|
+ @RequestParam Map<String, String> allParams,
|
|
|
|
|
+ HttpServletRequest request,
|
|
|
|
|
+ HttpServletResponse response) {
|
|
|
|
|
+
|
|
|
|
|
+ log.info("【新浪】接收监测请求 | params={}, ip={}",
|
|
|
|
|
+ allParams, getClientIp(request));
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 1. 提取新浪参数
|
|
|
|
|
+ String clickId = allParams.get("click_id");
|
|
|
|
|
+
|
|
|
|
|
+ if (StrUtil.isBlank(clickId)) {
|
|
|
|
|
+ log.warn("【新浪】监测请求缺少点击ID | params={}", allParams);
|
|
|
|
|
+ response.setStatus(HttpServletResponse.SC_OK);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 构建标准参数
|
|
|
|
|
+ Map<String, String> standardParams = new HashMap<>(allParams);
|
|
|
|
|
+ standardParams.put("click_id", clickId);
|
|
|
|
|
+ standardParams.put("source", "SINA");
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 保存点击追踪记录
|
|
|
|
|
+ Long siteId = extractSiteId(allParams);
|
|
|
|
|
+ clickTraceService.saveClickTrace(
|
|
|
|
|
+ siteId,
|
|
|
|
|
+ standardParams,
|
|
|
|
|
+ null,
|
|
|
|
|
+ getClientIp(request),
|
|
|
|
|
+ request.getHeader("User-Agent")
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ log.info("【新浪】监测请求处理成功 | clickId={}, siteId={}", clickId, siteId);
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 返回 200 OK
|
|
|
|
|
+ response.setStatus(HttpServletResponse.SC_OK);
|
|
|
|
|
+
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("【新浪】监测请求处理失败 | params={}", allParams, e);
|
|
|
|
|
+ response.setStatus(HttpServletResponse.SC_OK);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 腾讯广点通监测端点
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * 监测链接示例:
|
|
|
|
|
+ * https://track.yourdomain.com/api/track/gdt?click_id={CLICK_ID}&site_id=100&ad_id={ADGROUPID}
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * 广点通宏参数说明:
|
|
|
|
|
+ * - {CLICK_ID}: 点击ID(必填)
|
|
|
|
|
+ * - {ADGROUPID}: 广告组ID
|
|
|
|
|
+ * - {CREATIVEID}: 创意ID
|
|
|
|
|
+ * - {CAMPAIGNID}: 推广计划ID
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param allParams 所有URL参数
|
|
|
|
|
+ * @param request HTTP请求
|
|
|
|
|
+ * @param response HTTP响应
|
|
|
|
|
+ */
|
|
|
|
|
+ @GetMapping("/gdt")
|
|
|
|
|
+ public void trackGdt(
|
|
|
|
|
+ @RequestParam Map<String, String> allParams,
|
|
|
|
|
+ HttpServletRequest request,
|
|
|
|
|
+ HttpServletResponse response) {
|
|
|
|
|
+
|
|
|
|
|
+ log.info("【广点通】接收监测请求 | params={}, ip={}",
|
|
|
|
|
+ allParams, getClientIp(request));
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 1. 提取广点通参数(广点通使用大写参数名)
|
|
|
|
|
+ String clickId = allParams.get("click_id");
|
|
|
|
|
+ if (StrUtil.isBlank(clickId)) {
|
|
|
|
|
+ clickId = allParams.get("CLICK_ID");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (StrUtil.isBlank(clickId)) {
|
|
|
|
|
+ log.warn("【广点通】监测请求缺少点击ID | params={}", allParams);
|
|
|
|
|
+ response.setStatus(HttpServletResponse.SC_OK);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 构建标准参数
|
|
|
|
|
+ Map<String, String> standardParams = new HashMap<>(allParams);
|
|
|
|
|
+ standardParams.put("click_id", clickId);
|
|
|
|
|
+ standardParams.put("source", "GDT");
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 保存点击追踪记录
|
|
|
|
|
+ Long siteId = extractSiteId(allParams);
|
|
|
|
|
+ clickTraceService.saveClickTrace(
|
|
|
|
|
+ siteId,
|
|
|
|
|
+ standardParams,
|
|
|
|
|
+ null,
|
|
|
|
|
+ getClientIp(request),
|
|
|
|
|
+ request.getHeader("User-Agent")
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ log.info("【广点通】监测请求处理成功 | clickId={}, siteId={}", clickId, siteId);
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 返回 200 OK
|
|
|
|
|
+ response.setStatus(HttpServletResponse.SC_OK);
|
|
|
|
|
+
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("【广点通】监测请求处理失败 | params={}", allParams, e);
|
|
|
|
|
+ response.setStatus(HttpServletResponse.SC_OK);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 通用监测端点(兼容旧配置)
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * 如果广告平台已配置了统一的监测链接,仍可使用此端点
|
|
|
|
|
+ * 需要在参数中指定 source 参数
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * 示例:
|
|
|
|
|
+ * https://track.yourdomain.com/api/track/common?click_id=ABC123&site_id=100&source=baidu
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param allParams 所有URL参数
|
|
|
|
|
+ * @param request HTTP请求
|
|
|
|
|
+ * @param response HTTP响应
|
|
|
|
|
+ */
|
|
|
|
|
+ @GetMapping("/common")
|
|
|
|
|
+ public void trackCommon(
|
|
|
|
|
+ @RequestParam Map<String, String> allParams,
|
|
|
|
|
+ HttpServletRequest request,
|
|
|
|
|
+ HttpServletResponse response) {
|
|
|
|
|
+
|
|
|
|
|
+ String source = allParams.getOrDefault("source", "UNKNOWN");
|
|
|
|
|
+ log.info("【通用】接收监测请求 | source={}, params={}, ip={}",
|
|
|
|
|
+ source, allParams, getClientIp(request));
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 1. 提取点击ID(支持多种参数名)
|
|
|
|
|
+ String clickId = extractClickId(allParams);
|
|
|
|
|
+
|
|
|
|
|
+ if (StrUtil.isBlank(clickId)) {
|
|
|
|
|
+ log.warn("【通用】监测请求缺少点击ID | params={}", allParams);
|
|
|
|
|
+ response.setStatus(HttpServletResponse.SC_OK);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 确保参数中包含标准字段
|
|
|
|
|
+ Map<String, String> standardParams = new HashMap<>(allParams);
|
|
|
|
|
+ standardParams.put("click_id", clickId);
|
|
|
|
|
+ if (!standardParams.containsKey("source")) {
|
|
|
|
|
+ standardParams.put("source", "UNKNOWN");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 保存点击追踪记录
|
|
|
|
|
+ Long siteId = extractSiteId(allParams);
|
|
|
|
|
+ clickTraceService.saveClickTrace(
|
|
|
|
|
+ siteId,
|
|
|
|
|
+ standardParams,
|
|
|
|
|
+ null,
|
|
|
|
|
+ getClientIp(request),
|
|
|
|
|
+ request.getHeader("User-Agent")
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ log.info("【通用】监测请求处理成功 | clickId={}, siteId={}, source={}",
|
|
|
|
|
+ clickId, siteId, source);
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 返回 200 OK
|
|
|
|
|
+ response.setStatus(HttpServletResponse.SC_OK);
|
|
|
|
|
+
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("【通用】监测请求处理失败 | params={}", allParams, e);
|
|
|
|
|
+ response.setStatus(HttpServletResponse.SC_OK);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 提取点击ID(支持多种参数名)
|
|
|
|
|
+ */
|
|
|
|
|
+ private String extractClickId(Map<String, String> params) {
|
|
|
|
|
+ String clickId = params.get("click_id");
|
|
|
|
|
+ if (StrUtil.isBlank(clickId)) {
|
|
|
|
|
+ clickId = params.get("clickid");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StrUtil.isBlank(clickId)) {
|
|
|
|
|
+ clickId = params.get("clk_id");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StrUtil.isBlank(clickId)) {
|
|
|
|
|
+ clickId = params.get("clkid");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StrUtil.isBlank(clickId)) {
|
|
|
|
|
+ clickId = params.get("CLICK_ID");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StrUtil.isBlank(clickId)) {
|
|
|
|
|
+ clickId = params.get("callback_param");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StrUtil.isBlank(clickId)) {
|
|
|
|
|
+ clickId = params.get("logid");
|
|
|
|
|
+ }
|
|
|
|
|
+ return clickId;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 提取站点ID
|
|
|
|
|
+ */
|
|
|
|
|
+ private Long extractSiteId(Map<String, String> params) {
|
|
|
|
|
+ String siteIdStr = params.get("site_id");
|
|
|
|
|
+ if (StrUtil.isBlank(siteIdStr)) {
|
|
|
|
|
+ siteIdStr = params.get("siteid");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (StrUtil.isNotBlank(siteIdStr)) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return Long.parseLong(siteIdStr);
|
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
|
+ log.warn("站点ID格式错误:{}", siteIdStr);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取客户端真实IP
|
|
|
|
|
+ */
|
|
|
|
|
+ private String getClientIp(HttpServletRequest request) {
|
|
|
|
|
+ String ip = request.getHeader("X-Forwarded-For");
|
|
|
|
|
+ if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
|
|
|
|
|
+ ip = request.getHeader("X-Real-IP");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
|
|
|
|
|
+ ip = request.getHeader("Proxy-Client-IP");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
|
|
|
|
|
+ ip = request.getHeader("WL-Proxy-Client-IP");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
|
|
|
|
|
+ ip = request.getRemoteAddr();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 处理多个IP的情况,取第一个
|
|
|
|
|
+ if (StrUtil.isNotBlank(ip) && ip.contains(",")) {
|
|
|
|
|
+ ip = ip.split(",")[0].trim();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return ip;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|