策略模式企业级实践——广告业务逻辑实现
业务背景
广告投放系统需要对接多个广告平台(腾讯、字节跳动、快手等),处理不同平台的事件回传请求。核心业务需求:
- 多平台差异化处理:各平台参数格式、加密方式、回调协议不同
- 事件类型扩展:支持点击(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 ,依据这些参数来调用对应的策略) |
2 | publishEvent() | 参数标准化处理,生成统一AdEvent对象 |
3 | handlerAdEvent() | Spring事件监听器,触发异步处理 |
4 | handleAdEventMq() | 策略路由核心逻辑: 1. 解析platform_type+event_type 2. 查询策略注册表 |
5 | handleEvent() | 具体策略执行: - 参数解密 - 数据校验 - 业务处理 - 结果持久化 |
核心实现
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. 策略处理类
这段代码是广告事件处理的核心策略路由实现,主要职责是根据广告事件的平台类型和事件类型,动态选择并执行对应的策略处理器。
- 接收所有
EventStrategy
实现类的实例列表(通过Spring自动注入)。 - 遍历策略列表,按照
平台类型
和事件类型
构建双层Map。 - 最终生成完整的策略路由表,存储到
eventStrategyMaps
中。 - 根据广告事件的平台类型和事件类型,动态选择并执行对应的策略处理器。比如我这里就做了腾讯代码的业务筛选
/**
* 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