版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_38004638/article/details/91974842
8.4 广告检索服务
媒体方发起广告请求,检索服务检索广告数据(条件匹配过程),返回响应
媒体方的请求包含的三个要素 媒体方的请求标识 mediaId 请求基本信息 RequestInfo: requestId,adSlots, App,Geo,Device 匹配信息 FeatureInfo:KeywordFeature, DistrictFeature,ItFeature, FeatureRelation
8.4.1 媒体方请求对象的定义
定义检索服务中媒体方发起的请求对象
代码语言:javascript复制@Data
@NoArgsConstructor
@AllArgsConstructor
public class SearchRequest {
// 媒体方的请求标识
private String mediaId;
// 请求基本信息(广告位信息AdSlot、终端信息App、设备信息Device、地域信息Geo)
private RequestInfo requestInfo;
// 匹配信息(关键字、兴趣、地域)
private FeatureInfo featureInfo;
/**
* 请求信息
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class RequestInfo {
//唯一请求ID
private String requestId;
//基本请求信息
private List<AdSlot> adSlots;
private App app;
private Geo geo;
private Device device;
}
/**
* 匹配信息
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class FeatureInfo {
//根据关键字、地域、兴趣筛选推广单元
private KeywordFeature keywordFeature;
private DistrictFeature districtFeature;
private ItFeature itFeature;
//默认全部匹配
private FeatureRelation relation = FeatureRelation.AND;
}
}
广告位信息AdSlot
代码语言:javascript复制@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdSlot {
// 广告位编码
private String adSlotCode;
// 广告位置类型
private Integer positionType;
// 宽和高
private Integer width;
private Integer height;
// 广告物料类型: 图片, 视频
private List<Integer> type;
// 最低出价
private Integer minCpm;
}
应用终端信息App
代码语言:javascript复制@Data
@NoArgsConstructor
@AllArgsConstructor
public class App {
// 应用编码
private String appCode;
// 应用名称
private String appName;
// 应用包名
private String packageName;
// 请求页面 activity名称
private String activityName;
}
设备信息Device
代码语言:javascript复制@Data
@NoArgsConstructor
@AllArgsConstructor
public class Device {
// 设备 id
private String deviceCode;
// mac地址
private String mac;
// 设备ip
private String ip;
// 机型编码
private String model;
// 分辨率尺寸
private String displaySize;
// 屏幕尺寸
private String screenSize;
// 设备序列号
private String serialName;
}
地域信息Geo
代码语言:javascript复制@Data
@NoArgsConstructor
@AllArgsConstructor
public class Geo {
//纬度
private Float latitude;
//经度
private Float longitude;
//所在城市
private String city;
private String province;
}
8.4.2 检索服务响应对象的定义
检索服务中检索系统,根据媒体请求,返回响应
定义响应对象格式
匹配信息 FeatureInfo:KeywordFeature, DistrictFeature,ItFeature, FeatureRelation
代码语言:javascript复制@Data
@NoArgsConstructor
@AllArgsConstructor
public class SearchResponse {
public Map<String, List<Creative>> adSlot2Ads = new HashMap<>();
/**
* 返回创意信息
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Creative {
private Long adId;
private String adUrl;
private Integer width;
private Integer height;
private Integer type;
private Integer materialType;
// 展示监测 url:监测上架需要展示的广告
private List<String> showMonitorUrl = Arrays.asList("www.imooc.com", "www.imooc.com");
// 点击监测 url:监测需要点击的广告,类似手机开屏广告
private List<String> clickMonitorUrl = Arrays.asList("www.imooc.com", "www.imooc.com");
}
//广告创意索引对象CreativeObject -> 响应返回创意信息Creative
public static Creative convert(CreativeObject object) {
Creative creative = new Creative();
creative.setAdId(object.getAdId());
creative.setAdUrl(object.getAdUrl());
creative.setWidth(object.getWidth());
creative.setHeight(object.getHeight());
creative.setType(object.getType());
creative.setMaterialType(object.getMaterialType());
return creative;
}
}
广告二次筛选:地域DistrictFeature
代码语言:javascript复制@Data
@NoArgsConstructor
@AllArgsConstructor
public class DistrictFeature {
private List<ProvinceAndCity> districts;
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class ProvinceAndCity {
private String province;
private String city;
}
}
广告二次筛选:兴趣ItFeature
代码语言:javascript复制@Data
@NoArgsConstructor
@AllArgsConstructor
public class ItFeature {
private List<String> its;
}
广告二次筛选:关键字KeywordFeature
代码语言:javascript复制@Data
@NoArgsConstructor
@AllArgsConstructor
public class KeywordFeature {
private List<String> keywords;
}
广告筛选关系:全部匹配and,部分匹配or
代码语言:javascript复制public enum FeatureRelation {
OR,AND
}
广告单元对象的位置类型字段信息
代码语言:javascript复制public class AdUnitConstants {
public static class POSITION_TYPE {
//开屏(打卡应用)
public static final int KAIPING = 1;
//贴片(视频播放前)
public static final int TIEPIAN = 2;
//中贴(视频播放中间)
public static final int TIEPIAN_MIDDLE = 4;
//暂停贴(暂停时广告)
public static final int TIEPIAN_PAUSE = 8;
//后贴(视频播放完)
public static final int TIEPIAN_POST = 16;
}
}
在AdUnit索引对象AdUnitObject中添加isAdSlotTypeOK方法,判断检索请求对象adSlotType 和 广告单元索引AdUnit ,两者的positionType是否匹配
代码语言:javascript复制public static boolean isAdSlotTypeOK(int adSlotType, int positionType) {
switch (adSlotType) {
case AdUnitConstants.POSITION_TYPE.KAIPING:return isKaiPing(positionType);
case AdUnitConstants.POSITION_TYPE.TIEPIAN:return isTiePian(positionType);
case AdUnitConstants.POSITION_TYPE.TIEPIAN_MIDDLE:return isTiePianMiddle(positionType);
case AdUnitConstants.POSITION_TYPE.TIEPIAN_PAUSE:return isTiePianPause(positionType);
case AdUnitConstants.POSITION_TYPE.TIEPIAN_POST:return isTiePianPost(positionType);
default:return false;
}
}
/**
* 判断广告位置信息positionType是否匹配预定类型
*/
private static boolean isKaiPing(int positionType) {
return (positionType & AdUnitConstants.POSITION_TYPE.KAIPING) > 0;
}
private static boolean isTiePian(int positionType) {
return (positionType & AdUnitConstants.POSITION_TYPE.TIEPIAN) > 0;
}
private static boolean isTiePianMiddle(int positionType) {
return (positionType & AdUnitConstants.POSITION_TYPE.TIEPIAN_MIDDLE) > 0;
}
private static boolean isTiePianPause(int positionType) {
return (positionType & AdUnitConstants.POSITION_TYPE.TIEPIAN_PAUSE) > 0;
}
private static boolean isTiePianPost(int positionType) {
return (positionType & AdUnitConstants.POSITION_TYPE.TIEPIAN_POST) > 0;
}
在AdUnitIndex中匹配推广单元中位置信息positionType
代码语言:javascript复制private static Map<Long, AdUnitObject> objectMap;
static {objectMap = new ConcurrentHashMap<>();}
//匹配推广单元中位置信息positionType
public Set<Long> match(Integer positionType) {
Set<Long> adUnitIds = new HashSet<>();
//遍历所有索引对象,添加匹配positionType成功的推广单元
objectMap.forEach((k, v) -> {
if (AdUnitObject.isAdSlotTypeOK(positionType, v.getPositionType())) {
adUnitIds.add(k);
}
});return adUnitIds;
}
8.4.3 构造检索服务的响应对象
获取响应接口
代码语言:javascript复制public interface ISearch {
//获取广告创意数据
SearchResponse fetchAds(SearchRequest request);
}
检索服务的匹配过程 核心的思想是循环遍历媒体方请求的广告位,将匹配范围由大变小,越是能过滤更多的推广单元的条件匹配,越是先执行。对 于每一个广告位,匹配过程如下: 构造检索服务的响应对象,根据广告位置类型实现对推广单元的预筛选,根据匹配信息实现对推广单元的再筛选
通过推广单元获取关联的创意实现,填充检索服务响应对象
代码语言:javascript复制@Slf4j
@Service
public class SearchImpl implements ISearch {
public SearchResponse fallback(SearchRequest request, Throwable e) {
return null;
}
@Override
@HystrixCommand(fallbackMethod = "fallback")
public SearchResponse fetchAds(SearchRequest request) {
// 请求的广告位信息
List<AdSlot> adSlots = request.getRequestInfo().getAdSlots();
// 响应对象Feature
KeywordFeature keywordFeature = request.getFeatureInfo().getKeywordFeature();
DistrictFeature districtFeature = request.getFeatureInfo().getDistrictFeature();
ItFeature itFeature = request.getFeatureInfo().getItFeature();
FeatureRelation relation = request.getFeatureInfo().getRelation();
// 构造响应对象
SearchResponse response = new SearchResponse();
Map<String, List<SearchResponse.Creative>> adSlot2Ads = response.getAdSlot2Ads();
//遍历AdSlot
for (AdSlot adSlot : adSlots) {
Set<Long> targetUnitIdSet;
//根据广告位置类型一次筛选,获取初始推广单元AdUnit
Set<Long> adUnitIdSet = DataTable.of(AdUnitIndex.class).match(adSlot.getPositionType());
//根据(地域/兴趣/关键字)特征信息进行and/or二次筛选
if (relation == FeatureRelation.AND) {
filterKeywordFeature(adUnitIdSet, keywordFeature);
filterDistrictFeature(adUnitIdSet, districtFeature);
filterItTagFeature(adUnitIdSet, itFeature);
targetUnitIdSet = adUnitIdSet;
} else {
targetUnitIdSet = getORRelationUnitIds(adUnitIdSet, keywordFeature, districtFeature, itFeature);
}
//根据广告单元ID集合获取广告单元索引对象
List<AdUnitObject> unitObjects = DataTable.of(AdUnitIndex.class).fetch(targetUnitIdSet);
//判断广告单元状态,筛选无效的广告单元
filterAdUnitAndPlanStatus(unitObjects, CommonStatus.VALID);
//根据广告单元ID集合获取创意ID集合
List<Long> adIds = DataTable.of(CreativeUnitIndex.class).selectAds(unitObjects);
//根据创意ID集合获取创意对象集合
List<CreativeObject> creatives = DataTable.of(CreativeIndex.class).fetch(adIds);
//根据广告位信息AdSlot 实现对创意对象的过滤
filterCreativeByAdSlot(creatives, adSlot.getWidth(), adSlot.getHeight(), adSlot.getType());
adSlot2Ads.put(adSlot.getAdSlotCode(), buildCreativeResponse(creatives));
}
log.info("fetchAds: {}-{}", JSON.toJSONString(request), JSON.toJSONString(response));
return response;
}
/**
* 广告筛选关系为部分匹配
*/
private Set<Long> getORRelationUnitIds(Set<Long> adUnitIdSet, KeywordFeature keywordFeature,
DistrictFeature districtFeature, ItFeature itFeature) {
//判空
if (CollectionUtils.isEmpty(adUnitIdSet)) { return Collections.emptySet(); }
//将广告单元集合保存三个副本
Set<Long> keywordUnitIdSet = new HashSet<>(adUnitIdSet);
Set<Long> districtUnitIdSet = new HashSet<>(adUnitIdSet);
Set<Long> itUnitIdSet = new HashSet<>(adUnitIdSet);
//三个副本按照每个条件进行一次过滤
filterKeywordFeature(keywordUnitIdSet, keywordFeature);
filterDistrictFeature(districtUnitIdSet, districtFeature);
filterItTagFeature(itUnitIdSet, itFeature);
//返回三次过滤结果的并集合
return new HashSet<>(
CollectionUtils.union(
CollectionUtils.union(keywordUnitIdSet, districtUnitIdSet), itUnitIdSet
)
);
}
/**
* 根据关键字二次筛选广告单元
*/
private void filterKeywordFeature(Collection<Long> adUnitIds, KeywordFeature keywordFeature) {
//判空
if (CollectionUtils.isEmpty(adUnitIds)) { return; }
//遍历adUnitIds,调用UnitKeywordIndex中的match方法,判断adUnit中是否含该关键字
if (CollectionUtils.isNotEmpty(keywordFeature.getKeywords())) {
CollectionUtils.filter(adUnitIds, adUnitId ->
DataTable.of(UnitKeywordIndex.class).match(adUnitId, keywordFeature.getKeywords()));
}
}
/**
* 根据地域二次筛选广告单元
*/
private void filterDistrictFeature(Collection<Long> adUnitIds, DistrictFeature districtFeature) {
//判空
if (CollectionUtils.isEmpty(adUnitIds)) { return; }
//遍历adUnitIds,调用UnitDistrictIndex中的match方法,判断adUnit中是否含该地域
if (CollectionUtils.isNotEmpty(districtFeature.getDistricts())) {
CollectionUtils.filter(adUnitIds, adUnitId ->
DataTable.of(UnitDistrictIndex.class).match(adUnitId, districtFeature.getDistricts()));
}
}
/**
* 根据兴趣二次筛选广告单元
*/
private void filterItTagFeature(Collection<Long> adUnitIds, ItFeature itFeature) {
//判空
if (CollectionUtils.isEmpty(adUnitIds)) { return; }
//遍历adUnitIds,调用UnitDistrictIndex中的match方法,判断adUnit中是否含该地域
if (CollectionUtils.isNotEmpty(itFeature.getIts())) {
CollectionUtils.filter(adUnitIds, adUnitId ->
DataTable.of(UnitItIndex.class).match(adUnitId, itFeature.getIts()));
}
}
/**
* 判断广告单元状态,筛选无效的广告单元
*/
private void filterAdUnitAndPlanStatus(List<AdUnitObject> unitObjects, CommonStatus status) {
//判空
if (CollectionUtils.isEmpty(unitObjects)) { return; }
CollectionUtils.filter(
unitObjects, object -> object.getUnitStatus().equals(status.getStatus())
&& object.getAdPlanObject().getPlanStatus().equals(status.getStatus())
);
}
/**
* 通过广告位信息AdSlot 实现对创意对象的过滤
*/
private void filterCreativeByAdSlot(List<CreativeObject> creatives, Integer width,
Integer height, List<Integer> type) {
//判空
if (CollectionUtils.isEmpty(creatives)) { return; }
//筛选状态有效,符合广告位定义的宽高和类型的创意对象
CollectionUtils.filter(creatives, creative ->
creative.getAuditStatus().equals(CommonStatus.VALID.getStatus())
&& creative.getWidth().equals(width)
&& creative.getHeight().equals(height)
&& type.contains(creative.getType())
);
}
/**
* 一个广告位对应一个广告创意
* 实现过滤的广告创意对象,转换成检索系统返回响应中的创意信息
* CreativeObject -> SearchResponse : Creative
*/
private List<SearchResponse.Creative> buildCreativeResponse(List<CreativeObject> creatives) {
//判空
if (CollectionUtils.isEmpty(creatives)) { return Collections.emptyList(); }
CreativeObject randomObject = creatives.get(Math.abs(new Random().nextInt()) % creatives.size());
return Collections.singletonList(SearchResponse.convert(randomObject));
}
}
8.4.5 完善广告检索服务入口
在SearchController中定义服务入口,根据请求,返回响应
代码语言:javascript复制@Autowired
private final ISearch search;
@PostMapping("/fetchAds")
public SearchResponse fetchAds(@RequestBody SearchRequest request){
log.info("ad-search: fetchAds -> {}",JSON.toJSONString(request));
return search.fetchAds(request);
}
在ad-gateway网关中resources/application.yml定义
代码语言:javascript复制#检索系统
search:
path: /ad-search/**
serviceId: eureka-client-ad-search
strip-prefix: false