广告点击事件处理方案——策略模式驱动的Java企业级实现

114 阅读4分钟

策略模式企业级实践——广告业务逻辑实现

业务背景

广告投放系统需要对接多个广告平台(腾讯、字节跳动、快手等),处理不同平台的事件回传请求。核心业务需求:

  • 多平台差异化处理:各平台参数格式、加密方式、回调协议不同
  • 事件类型扩展:支持点击(CLICK)、激活(ACTIVE)、支付(PAY)等20+事件类型
  • 高并发处理:日均处理千万级事件,峰值QPS>5000
  • 数据一致性:保证跨平台点击数据与订单数据的精准关联

业务例子:比如说用户点击了广告页,就会触发对应的URL(我们这边提供),第三方系统腾讯就会调用我们的URL,回传的数据是用户点击数据(包括设备编号,时间等等内容)。

通过采用策略模式和事件驱动的设计,实现了对不同类型广告事件的灵活处理和扩展。

请求处理流程图

graph TD
    A((用户请求)) --> B[/conversion/tracking接口/]
    B --> C[AdEventController.publishEvent]
    C --> D{事件发布RabbitMQ}
    D -->|Spring事件机制| E[AdEventListener.handlerAdEvent]
    
    E --> F[AdEventHandler.handleAdEventMq]
    F --> G{策略路由决策}
    G -->|平台类型=腾讯<br>事件类型=APP_CLICK| H[TencentClickAppEvent策略实例]
    G -->|平台类型=抖音| I[DouyinClickAppEvent策略实例]
    G -->|平台类型=快手| J[KuaishouClickAppEvent策略实例]
    
    H --> K[执行腾讯特有逻辑]
    I --> L[执行抖音特有逻辑]
    J --> M[执行快手特有逻辑]
    
    K --> N[[数据存储]]
    L --> N
    M --> N
    
    style B fill:#81D4FA,stroke:#039BE5
    style E fill:#C8E6C9,stroke:#4CAF50
    style G fill:#FFE0B2,stroke:#FB8C00
    classDef strategy fill:#E1BEE7,stroke:#9C27B0;
    class H,I,J strategy;

架构设计

策略模式架构

graph TD
    A[广告平台] --> B(调用对应的URL)
    B --> C{事件路由器}
    C -->|平台+事件类型| D[策略处理器]
    D --> E[数据存储]
    C -->|异常事件| F[异常处理中心]
    
    classDef strategy fill:#E3F2FD,stroke:#2196F3;
    class D strategy;

流程说明

步骤组件/方法关键逻辑
1/conversion/tracking统一接入端点,接收所有平台事件请求(url请求参数会像xxx.com?platformType=tencent&adSourceType=tencent_app ,依据这些参数来调用对应的策略)
2publishEvent()参数标准化处理,生成统一AdEvent对象
3handlerAdEvent()Spring事件监听器,触发异步处理
4handleAdEventMq()策略路由核心逻辑:
1. 解析platform_type+event_type
2. 查询策略注册表
5handleEvent()具体策略执行:
- 参数解密
- 数据校验
- 业务处理
- 结果持久化

核心实现

1. 发布服务类(Contrller层跳到此处)

/**
 * @Author: 
 */
@Service
@RequiredArgsConstructor(onConstructor = @_(@Autowired))
public class EventPublisherServiceImpl implements EventPublisherService {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final RabbitTemplate rabbitTemplate;
    private final ApplicationEventPublisher eventPublisher;

    @Override
    public void publishEvent(AdEvent event) {
        try {
            rabbitTemplate.convertAndSend(RabbitConstants.AD_EVENT_QUEUE, event);
        } catch (Exception e) {
            logger.error("MQ消息发送失败,使用发布订阅模式:{}", e.getMessage(), e);
            logger.error("MQ消息发送失败,当前事件:{}", JSONObject.toJSONString(event));
            eventPublisher.publishEvent(event);
        }
    }
    }

2. 监听服务类(RabbitMQ队列监听)


@Component
@RequiredArgsConstructor
public class AdEventListener {

    @RabbitListener(queuesToDeclare = @Queue(AD_EVENT_QUEUE))
    public void handlerAdEvent(AdEvent event, Message message, Channel channel) throws IOException {
        try {
            eventHandler.handleAdEventMq(event);
        } catch (Exception e) {
            logger.error("Handle order conversion event: Error occurs: {}", e.getMessage(), e);
        } finally {
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }
    }
}

3. 策略处理类

这段代码是广告事件处理的核心策略路由实现,主要职责是根据广告事件的平台类型和事件类型,动态选择并执行对应的策略处理器。

  1. 接收所有EventStrategy实现类的实例列表(通过Spring自动注入)。
  2. 遍历策略列表,按照平台类型事件类型构建双层Map。
  3. 最终生成完整的策略路由表,存储到eventStrategyMaps中。
  4. 根据广告事件的平台类型和事件类型,动态选择并执行对应的策略处理器。比如我这里就做了腾讯代码的业务筛选

/**
 * AdEventHandler
 *
 * @Author: 
 */
@Component
public class AdEventHandler {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final Map<String, Map<String, EventStrategy>> eventStrategyMaps;
    @Resource
    private AdEventAspect aspect;

    public AdEventHandler(List<EventStrategy> eventStrategies) {
        logger.info("EventStrategy size:{}", eventStrategies.size());
        Map<String, Map<String, EventStrategy>> strategyMaps = new HashMap<>();
        eventStrategies.forEach(strategy -> {
            String platform = strategy.getPlatformType();
            String event = strategy.getEventType();
            if (!strategyMaps.containsKey(platform)) {
                strategyMaps.put(platform, new HashMap<>());
            }
            strategyMaps.get(platform).put(event, strategy);
        });
        eventStrategyMaps = strategyMaps;
    }

    public void handleAdEventMq(AdEvent adEvent) {
        if (!eventStrategyMaps.containsKey(adEvent.getPlatformType())) {
            logger.error("Unknown platform type:{}", JSONObject.toJSON(adEvent));
            return;
        }
        final Map<String, EventStrategy> eventStrategyMap = eventStrategyMaps.get(adEvent.getPlatformType());
        if (!eventStrategyMap.containsKey(adEvent.getEventType())) {
            logger.error("Unknown event type:{}", JSONObject.toJSON(adEvent));
            return;
        }
        EventStrategy eventStrategy = eventStrategyMap.get(adEvent.getEventType());

        boolean isTencent = AdPlatformTypeEnum.TENCENT.getCode().equals(adEvent.getPlatformType());
        if (isTencent && !StrUtil.equals(AdEventTypeEnum.CLICK.getCode(), adEvent.getEventType())) {
            aspect.autofillEntity(adEvent, getIdentity(adEvent), "entity");
            TencentClickRecord entity = adEvent.get("entity", TencentClickRecord.class);
            if (entity != null && AdSourceTypeEnum.TENCENT_MINI_SEARCH.getCode().equals(entity.getAdSourceType())) {
                adEvent.setEventType(adEvent.getEventType().concat("_search"));
                eventStrategy = eventStrategyMap.get(adEvent.getEventType());
            }

        }
        logger.info("=============Handle ad event=============\nplatform type: {}\nevent type: {}\nad source type: {}\nevent data: {}",
                adEvent.getPlatformType(), adEvent.getEventType(), adEvent.getAdSourceType(), adEvent.getEventData()
        );
        eventStrategy.handleEvent(adEvent);
    }
    }

策略接口定义

public interface EventStrategy {
    /**
     * 获取平台类型标识
     */
    String getPlatformType();
    
    /**
     * 支持处理的事件类型
     */
    String getEventType();
    
    /**
     * 核心处理方法
     * @param event 标准化事件对象
     */
    Boolean handleEvent(AdEvent event);
    
    /**
     * 降级处理(模板方法)
     */
    default Boolean fallbackHandle(AdEvent event) {
        // 默认降级逻辑...
    }
}

具体策略实现(以腾讯点击为例子)

腾讯广告点击事件处理器

@Component
public class TencentClickEvent implements EventStrategy {
    private static final String PLATFORM = AdPlatformTypeEnum.TENCENT.getCode();
    private static final String EVENT_TYPE = AdEventTypeEnum.CLICK.getCode();

    @Autowired
    private TencentDecryptService decryptService;

    @Override
    public Boolean handleEvent(AdEvent event) {
        // 1. 参数解密
        Map<String, String> params = decryptService.decrypt(event.getData());
        
        // 2. 数据清洗
        TencentClickRecord record = convertToRecord(params);
        
        // 3. 存储核心数据
        return tencentStorageService.saveClickRecord(record);
    }
    
    // 实现接口方法
    public String getPlatformType() { return PLATFORM; }
    public String getEventType() { return EVENT_TYPE; }
}
graph LR
    A[TencentStrategy] -->|根据枚举类注册到| B(策略容器)
    C[DouyinStrategy] -->|根据枚举类注册到到| B
    D[KuaishouStrategy] -->|根据枚举类注册到| B
    
    B --> E{策略路由器}
    E -->|运行时动态选择| F[具体策略]

优化点

  • 策略路由可以专门建立一个类,无需和AdHandle耦合起来(注册中心)
  • 配置优先级(如果业务较多,属于同一个点击事件的触发时机的先后顺序)
    • 解决:
      • AOP实现处理方法:每个策略类实现一个order方法,返回对应的顺序
      • 再加上对应注解,按每个策略类返回的order序号来处理

写文不易,也麻烦点个赞啦~ :P