|
|
@@ -0,0 +1,278 @@
|
|
|
+package shop.alien.store.service.impl;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import shop.alien.entity.result.R;
|
|
|
+import shop.alien.entity.store.OceanEngineClickMonitorLog;
|
|
|
+import shop.alien.mapper.OceanEngineClickMonitorLogMapper;
|
|
|
+import shop.alien.store.dto.OceanEngineClickBindUserDto;
|
|
|
+import shop.alien.store.service.OceanEngineClickMonitorLogService;
|
|
|
+
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.Enumeration;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 巨量引擎点击监测服务实现
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class OceanEngineClickMonitorLogServiceImpl
|
|
|
+ extends ServiceImpl<OceanEngineClickMonitorLogMapper, OceanEngineClickMonitorLog>
|
|
|
+ implements OceanEngineClickMonitorLogService {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R<Long> receiveClickMonitor(HttpServletRequest request) {
|
|
|
+ Map<String, String> paramMap = extractQueryParams(request);
|
|
|
+ log.info("OceanEngineClickMonitorLogServiceImpl.receiveClickMonitor, paramCount={}, queryString={}",
|
|
|
+ paramMap.size(), request.getQueryString());
|
|
|
+
|
|
|
+ String clickId = firstNonBlank(paramMap,
|
|
|
+ "clickid", "click_id", "CLICKID");
|
|
|
+ String callbackParam = firstNonBlank(paramMap,
|
|
|
+ "callback", "callback_param", "CALLBACK_PARAM");
|
|
|
+ String requestId = firstNonBlank(paramMap,
|
|
|
+ "request_id", "req_id", "REQUEST_ID");
|
|
|
+
|
|
|
+ // 幂等:相同 click_id 或 request_id 已存在则直接返回
|
|
|
+ OceanEngineClickMonitorLog existing = findExistingLog(clickId, requestId);
|
|
|
+ if (existing != null) {
|
|
|
+ log.info("OceanEngineClickMonitorLogServiceImpl.receiveClickMonitor, duplicate click, id={}, clickId={}",
|
|
|
+ existing.getId(), clickId);
|
|
|
+ return R.data(existing.getId(), "重复点击,已忽略");
|
|
|
+ }
|
|
|
+
|
|
|
+ OceanEngineClickMonitorLog clickLog = buildClickLog(request, paramMap, clickId, callbackParam, requestId);
|
|
|
+ boolean saved = this.save(clickLog);
|
|
|
+ if (!saved) {
|
|
|
+ log.error("OceanEngineClickMonitorLogServiceImpl.receiveClickMonitor, save failed, clickId={}", clickId);
|
|
|
+ return R.fail("点击监测日志保存失败");
|
|
|
+ }
|
|
|
+ log.info("OceanEngineClickMonitorLogServiceImpl.receiveClickMonitor, saved id={}, clickId={}, advertiserId={}",
|
|
|
+ clickLog.getId(), clickLog.getClickId(), clickLog.getAdvertiserId());
|
|
|
+ return R.data(clickLog.getId(), "接收成功");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R<String> bindUser(OceanEngineClickBindUserDto bindDto) {
|
|
|
+ log.info("OceanEngineClickMonitorLogServiceImpl.bindUser, param={}", bindDto);
|
|
|
+ if (bindDto == null || bindDto.getUserId() == null) {
|
|
|
+ return R.fail("userId不能为空");
|
|
|
+ }
|
|
|
+ if (StringUtils.isAllBlank(bindDto.getClickId(), bindDto.getCallbackParam())) {
|
|
|
+ return R.fail("clickId与callbackParam至少填一个");
|
|
|
+ }
|
|
|
+
|
|
|
+ OceanEngineClickMonitorLog clickLog = findClickLog(bindDto.getClickId(), bindDto.getCallbackParam());
|
|
|
+ if (clickLog == null) {
|
|
|
+ log.warn("OceanEngineClickMonitorLogServiceImpl.bindUser, click log not found, clickId={}, callbackParam={}",
|
|
|
+ bindDto.getClickId(), bindDto.getCallbackParam());
|
|
|
+ return R.fail("未找到对应的点击监测记录");
|
|
|
+ }
|
|
|
+
|
|
|
+ clickLog.setUserId(bindDto.getUserId());
|
|
|
+ clickLog.setStoreId(bindDto.getStoreId());
|
|
|
+ clickLog.setBindStatus(OceanEngineClickMonitorLog.BIND_STATUS_BOUND);
|
|
|
+ clickLog.setBindTime(new Date());
|
|
|
+ boolean updated = this.updateById(clickLog);
|
|
|
+ return updated ? R.success("绑定成功") : R.fail("绑定失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R<OceanEngineClickMonitorLog> getInfoById(Long id) {
|
|
|
+ log.info("OceanEngineClickMonitorLogServiceImpl.getInfoById, id={}", id);
|
|
|
+ return R.data(this.getById(id));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R<IPage<OceanEngineClickMonitorLog>> list(Integer pageNum, Integer pageSize,
|
|
|
+ String clickId, String advertiserId,
|
|
|
+ Integer userId, Integer bindStatus) {
|
|
|
+ log.info("OceanEngineClickMonitorLogServiceImpl.list, pageNum={}, pageSize={}, clickId={}, advertiserId={}, userId={}, bindStatus={}",
|
|
|
+ pageNum, pageSize, clickId, advertiserId, userId, bindStatus);
|
|
|
+ Page<OceanEngineClickMonitorLog> page = new Page<>(pageNum, pageSize);
|
|
|
+ LambdaQueryWrapper<OceanEngineClickMonitorLog> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ if (StringUtils.isNotBlank(clickId)) {
|
|
|
+ wrapper.eq(OceanEngineClickMonitorLog::getClickId, clickId);
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(advertiserId)) {
|
|
|
+ wrapper.eq(OceanEngineClickMonitorLog::getAdvertiserId, advertiserId);
|
|
|
+ }
|
|
|
+ if (userId != null) {
|
|
|
+ wrapper.eq(OceanEngineClickMonitorLog::getUserId, userId);
|
|
|
+ }
|
|
|
+ if (bindStatus != null) {
|
|
|
+ wrapper.eq(OceanEngineClickMonitorLog::getBindStatus, bindStatus);
|
|
|
+ }
|
|
|
+ wrapper.orderByDesc(OceanEngineClickMonitorLog::getCreatedTime);
|
|
|
+ return R.data(this.page(page, wrapper));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从 HttpServletRequest 提取全部 Query 参数
|
|
|
+ */
|
|
|
+ private Map<String, String> extractQueryParams(HttpServletRequest request) {
|
|
|
+ Map<String, String> paramMap = new LinkedHashMap<>();
|
|
|
+ Enumeration<String> names = request.getParameterNames();
|
|
|
+ while (names.hasMoreElements()) {
|
|
|
+ String name = names.nextElement();
|
|
|
+ paramMap.put(name, request.getParameter(name));
|
|
|
+ }
|
|
|
+ return paramMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建点击监测日志实体
|
|
|
+ */
|
|
|
+ private OceanEngineClickMonitorLog buildClickLog(HttpServletRequest request,
|
|
|
+ Map<String, String> paramMap,
|
|
|
+ String clickId,
|
|
|
+ String callbackParam,
|
|
|
+ String requestId) {
|
|
|
+ OceanEngineClickMonitorLog clickLog = new OceanEngineClickMonitorLog();
|
|
|
+ clickLog.setClickId(clickId);
|
|
|
+ clickLog.setCallbackParam(callbackParam);
|
|
|
+ clickLog.setRequestId(requestId);
|
|
|
+ clickLog.setAdvertiserId(firstNonBlank(paramMap, "advertiser_id", "ADVERTISER_ID"));
|
|
|
+ clickLog.setCampaignId(firstNonBlank(paramMap, "campaign_id", "CAMPAIGN_ID"));
|
|
|
+ clickLog.setAid(firstNonBlank(paramMap, "aid", "adid", "AID"));
|
|
|
+ clickLog.setCid(firstNonBlank(paramMap, "cid", "creativeid", "CID"));
|
|
|
+ clickLog.setProjectId(firstNonBlank(paramMap, "project_id", "PROJECT_ID"));
|
|
|
+ clickLog.setPromotionId(firstNonBlank(paramMap, "promotion_id", "PROMOTION_ID"));
|
|
|
+ clickLog.setConvertId(firstNonBlank(paramMap, "convert_id", "CONVERT_ID"));
|
|
|
+ clickLog.setCsite(firstNonBlank(paramMap, "csite", "CSITE"));
|
|
|
+ clickLog.setUnionSite(firstNonBlank(paramMap, "union_site", "UNION_SITE"));
|
|
|
+ clickLog.setMid1(firstNonBlank(paramMap, "mid1", "MID1"));
|
|
|
+ clickLog.setMid2(firstNonBlank(paramMap, "mid2", "MID2"));
|
|
|
+ clickLog.setMid3(firstNonBlank(paramMap, "mid3", "MID3"));
|
|
|
+ clickLog.setMid4(firstNonBlank(paramMap, "mid4", "MID4"));
|
|
|
+ clickLog.setMid5(firstNonBlank(paramMap, "mid5", "MID5"));
|
|
|
+ clickLog.setMid6(firstNonBlank(paramMap, "mid6", "MID6"));
|
|
|
+ clickLog.setClickTs(parseLong(firstNonBlank(paramMap, "ts", "TIMESTAMP", "TS")));
|
|
|
+ clickLog.setClickTsMs(parseLong(firstNonBlank(paramMap, "ts_ms", "TS_MS")));
|
|
|
+ clickLog.setClientIp(firstNonBlank(paramMap, "ip", "IP"));
|
|
|
+ clickLog.setClientIpv4(firstNonBlank(paramMap, "ipv4", "IPV4"));
|
|
|
+ clickLog.setClientIpv6(firstNonBlank(paramMap, "ipv6", "IPV6"));
|
|
|
+ clickLog.setUserAgent(firstNonBlank(paramMap, "ua", "UA"));
|
|
|
+ clickLog.setOsType(firstNonBlank(paramMap, "os", "OS"));
|
|
|
+ clickLog.setDeviceModel(firstNonBlank(paramMap, "model", "MODEL"));
|
|
|
+ clickLog.setIdfa(firstNonBlank(paramMap, "idfa", "IDFA"));
|
|
|
+ clickLog.setImei(firstNonBlank(paramMap, "imei", "IMEI"));
|
|
|
+ clickLog.setOaid(firstNonBlank(paramMap, "oaid", "OAID"));
|
|
|
+ clickLog.setOaidMd5(firstNonBlank(paramMap, "oaid_md5", "OAID_MD5"));
|
|
|
+ clickLog.setAndroidId(firstNonBlank(paramMap, "android_id", "ANDROIDID", "ANDROIDID1"));
|
|
|
+ clickLog.setAdVersion(resolveAdVersion(clickLog));
|
|
|
+ clickLog.setBindStatus(OceanEngineClickMonitorLog.BIND_STATUS_UNBOUND);
|
|
|
+ clickLog.setMonitorUrl(buildMonitorUrl(request));
|
|
|
+ clickLog.setRawParams(JSONObject.toJSONString(paramMap));
|
|
|
+ clickLog.setServerIp(resolveClientIp(request));
|
|
|
+ return clickLog;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 推断广告版本:2.0 有 project_id/promotion_id,1.0 有 aid/cid/campaign_id
|
|
|
+ */
|
|
|
+ private Integer resolveAdVersion(OceanEngineClickMonitorLog clickLog) {
|
|
|
+ if (StringUtils.isNotBlank(clickLog.getProjectId())
|
|
|
+ || StringUtils.isNotBlank(clickLog.getPromotionId())) {
|
|
|
+ return OceanEngineClickMonitorLog.AD_VERSION_V2;
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(clickLog.getAid())
|
|
|
+ || StringUtils.isNotBlank(clickLog.getCid())
|
|
|
+ || StringUtils.isNotBlank(clickLog.getCampaignId())) {
|
|
|
+ return OceanEngineClickMonitorLog.AD_VERSION_V1;
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private OceanEngineClickMonitorLog findExistingLog(String clickId, String requestId) {
|
|
|
+ if (StringUtils.isNotBlank(clickId)) {
|
|
|
+ OceanEngineClickMonitorLog byClickId = this.getOne(new LambdaQueryWrapper<OceanEngineClickMonitorLog>()
|
|
|
+ .eq(OceanEngineClickMonitorLog::getClickId, clickId)
|
|
|
+ .last("LIMIT 1"));
|
|
|
+ if (byClickId != null) {
|
|
|
+ return byClickId;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(requestId)) {
|
|
|
+ return this.getOne(new LambdaQueryWrapper<OceanEngineClickMonitorLog>()
|
|
|
+ .eq(OceanEngineClickMonitorLog::getRequestId, requestId)
|
|
|
+ .last("LIMIT 1"));
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private OceanEngineClickMonitorLog findClickLog(String clickId, String callbackParam) {
|
|
|
+ if (StringUtils.isNotBlank(clickId)) {
|
|
|
+ OceanEngineClickMonitorLog log = this.getOne(new LambdaQueryWrapper<OceanEngineClickMonitorLog>()
|
|
|
+ .eq(OceanEngineClickMonitorLog::getClickId, clickId)
|
|
|
+ .orderByDesc(OceanEngineClickMonitorLog::getCreatedTime)
|
|
|
+ .last("LIMIT 1"));
|
|
|
+ if (log != null) {
|
|
|
+ return log;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(callbackParam)) {
|
|
|
+ return this.getOne(new LambdaQueryWrapper<OceanEngineClickMonitorLog>()
|
|
|
+ .eq(OceanEngineClickMonitorLog::getCallbackParam, callbackParam)
|
|
|
+ .orderByDesc(OceanEngineClickMonitorLog::getCreatedTime)
|
|
|
+ .last("LIMIT 1"));
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String buildMonitorUrl(HttpServletRequest request) {
|
|
|
+ String queryString = request.getQueryString();
|
|
|
+ if (StringUtils.isBlank(queryString)) {
|
|
|
+ return request.getRequestURL().toString();
|
|
|
+ }
|
|
|
+ return request.getRequestURL() + "?" + queryString;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析客户端真实 IP(兼容反向代理)
|
|
|
+ */
|
|
|
+ private String resolveClientIp(HttpServletRequest request) {
|
|
|
+ String ip = request.getHeader("X-Forwarded-For");
|
|
|
+ if (StringUtils.isNotBlank(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
|
|
+ return ip.split(",")[0].trim();
|
|
|
+ }
|
|
|
+ ip = request.getHeader("X-Real-IP");
|
|
|
+ if (StringUtils.isNotBlank(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
|
|
+ return ip;
|
|
|
+ }
|
|
|
+ return request.getRemoteAddr();
|
|
|
+ }
|
|
|
+
|
|
|
+ private String firstNonBlank(Map<String, String> paramMap, String... keys) {
|
|
|
+ for (String key : keys) {
|
|
|
+ String value = paramMap.get(key);
|
|
|
+ if (StringUtils.isNotBlank(value)) {
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Long parseLong(String value) {
|
|
|
+ if (StringUtils.isBlank(value)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ return Long.parseLong(value);
|
|
|
+ } catch (NumberFormatException ex) {
|
|
|
+ log.warn("OceanEngineClickMonitorLogServiceImpl.parseLong, invalid number={}", value);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|