根据三个维度继续过滤

在上一节中我们实现了根据流量信息过滤的代码,但是我们的条件有可能是多条件一起传给我们的检索服务的,本节我们继续实现根据推广单元的三个维度条件的过滤。

  • SearchImpl类中添加过滤方法
public class SearchImpl implements ISearch {
@Override
public SearchResponse fetchAds(SearchRequest request) {
...
// 根据三个维度过滤
if (featureRelation == FeatureRelation.AND) {
filterKeywordFeature(adUnitIdSet, keywordFeature);
filterHobbyFeature(adUnitIdSet, hobbyFeatrue);
filterDistrictFeature(adUnitIdSet, districtFeature); targetUnitIdSet = adUnitIdSet;
} else {
getOrRelationUnitIds(adUnitIdSet, keywordFeature, hobbyFeatrue, districtFeature);
}
}
return null;
}
  • 定义三个方法实现过滤
/**
* 获取三个维度各自满足时的广告id
*/
private Set<Long> getOrRelationUnitIds(Set<Long> adUnitIdsSet,
KeywordFeature keywordFeature,
HobbyFeatrue hobbyFeatrue,
DistrictFeature districtFeature) {
if (CollectionUtils.isEmpty(adUnitIdsSet)) return Collections.EMPTY_SET; // 我们在处理的时候,需要对副本进行处理,大家可以考虑一下为什么需要这么做?
Set<Long> keywordUnitIdSet = new HashSet<>(adUnitIdsSet);
Set<Long> hobbyUnitIdSet = new HashSet<>(adUnitIdsSet);
Set<Long> districtUnitIdSet = new HashSet<>(adUnitIdsSet); filterKeywordFeature(keywordUnitIdSet, keywordFeature);
filterHobbyFeature(hobbyUnitIdSet, hobbyFeatrue);
filterDistrictFeature(districtUnitIdSet, districtFeature); // 返回它们的并集
return new HashSet<>(
CollectionUtils.union(
CollectionUtils.union(keywordUnitIdSet, hobbyUnitIdSet),
districtUnitIdSet
)
);
} /**
* 根据传递的关键词过滤
*/
private void filterKeywordFeature(Collection<Long> adUnitIds, KeywordFeature keywordFeature) {
if (CollectionUtils.isEmpty(adUnitIds)) return;
if (CollectionUtils.isNotEmpty(keywordFeature.getKeywords())) {
// 如果存在需要过滤的关键词,查找索引实例对象进行过滤处理
CollectionUtils.filter(
adUnitIds,
adUnitId -> IndexDataTableUtils.of(UnitKeywordIndexAwareImpl.class)
.match(adUnitId, keywordFeature.getKeywords())
);
}
} /**
* 根据传递的兴趣信息过滤
*/
private void filterHobbyFeature(Collection<Long> adUnitIds, HobbyFeatrue hobbyFeatrue) {
if (CollectionUtils.isEmpty(adUnitIds)) return;
// 如果存在需要过滤的兴趣,查找索引实例对象进行过滤处理
if (CollectionUtils.isNotEmpty(hobbyFeatrue.getHobbys())) {
CollectionUtils.filter(
adUnitIds,
adUnitId -> IndexDataTableUtils.of(UnitHobbyIndexAwareImpl.class)
.match(adUnitId, hobbyFeatrue.getHobbys())
);
}
} /**
* 根据传递的地域信息过滤
*/
private void filterDistrictFeature(Collection<Long> adUnitIds, DistrictFeature districtFeature) {
if (CollectionUtils.isEmpty(adUnitIds)) return;
// 如果存在需要过滤的地域信息,查找索引实例对象进行过滤处理
if (CollectionUtils.isNotEmpty(districtFeature.getProvinceAndCities())) {
CollectionUtils.filter(
adUnitIds,
adUnitId -> {
return IndexDataTableUtils.of(UnitDistrictIndexAwareImpl.class)
.match(adUnitId, districtFeature.getProvinceAndCities());
}
);
}
}
根据推广单元id获取推广创意

我们知道,推广单元和推广创意的关系是多对多,从上文我们查询到了推广单元ids,接下来我们实现根据推广单元id获取推广创意的代码,let's code.

首先,我们需要在com.sxzhongf.ad.index.creative_relation_unit.CreativeRelationUnitIndexAwareImpl 关联索引中查到推广创意的ids

 /**
* 通过推广单元id获取推广创意id
*/
public List<Long> selectAdCreativeIds(List<AdUnitIndexObject> unitIndexObjects) {
if (CollectionUtils.isEmpty(unitIndexObjects)) return Collections.emptyList(); //获取要返回的广告创意ids
List<Long> result = new ArrayList<>();
for (AdUnitIndexObject unitIndexObject : unitIndexObjects) {
//根据推广单元id获取推广创意
Set<Long> adCreativeIds = unitRelationCreativeMap.get(unitIndexObject.getUnitId());
if (CollectionUtils.isNotEmpty(adCreativeIds)) result.addAll(adCreativeIds);
} return result;
}

然后得到了推广创意的id list后,我们在创意索引实现类com.sxzhongf.ad.index.creative.CreativeIndexAwareImpl中定义根据ids查询创意的方法。

/**
* 根据ids获取创意list
*/
public List<CreativeIndexObject> findAllByIds(Collection<Long> ids) {
if (CollectionUtils.isEmpty(ids)) return Collections.emptyList();
List<CreativeIndexObject> result = new ArrayList<>(); for (Long id : ids) {
CreativeIndexObject object = get(id);
if (null != object)
result.add(object);
} return result;
}

自此,我们已经得到了想要的推广单元和推广创意,因为推广单元包含了推广计划,所以我们想要的数据已经全部可以获取到了,接下来,我们还得过滤一次当前我们查询到的数据的状态,因为有的数据,我们可能已经进行过逻辑删除了,因此还需要判断获取的数据是否有效。在SearchImpl类中实现。

  /**
* 根据状态信息过滤数据
*/
private void filterAdUnitAndPlanStatus(List<AdUnitIndexObject> unitIndexObjects, CommonStatus status) {
if (CollectionUtils.isEmpty(unitIndexObjects)) return; //同时判断推广单元和推广计划的状态
CollectionUtils.filter(
unitIndexObjects,
unitIndexObject -> unitIndexObject.getUnitStatus().equals(status.getStatus()) &&
unitIndexObject.getAdPlanIndexObject().getPlanStatus().equals(status.getStatus())
);
}

SearchImpl中我们实现广告创意的查询.

...

//获取 推广计划 对象list
List<AdUnitIndexObject> unitIndexObjects = IndexDataTableUtils.of(AdUnitIndexAwareImpl.class).fetch(adUnitIdSet);
//根据状态过滤数据
filterAdUnitAndPlanStatus(unitIndexObjects, CommonStatus.VALID);
//获取 推广创意 id list
List<Long> creativeIds = IndexDataTableUtils.of(CreativeRelationUnitIndexAwareImpl.class)
.selectAdCreativeIds(unitIndexObjects);
//根据 推广创意ids获取推广创意
List<CreativeIndexObject> creativeIndexObjects = IndexDataTableUtils.of(CreativeIndexAwareImpl.class)
...
根据广告位adslot 实现对创意数据的过滤

因为我们的广告位是有不同的大小,不同的类型,因此,我们在获取到所有符合我们查询维度以及流量类型的条件后,还需要针对不同的广告位来展示不同的广告创意信息。

/**
* 根据广告位类型以及参数获取展示的合适广告信息
*
* @param creativeIndexObjects 所有广告创意
* @param width 广告位width
* @param height 广告位height
*/
private void filterCreativeByAdSlot(List<CreativeIndexObject> creativeIndexObjects,
Integer width,
Integer height,
List<Integer> type) {
if (CollectionUtils.isEmpty(creativeIndexObjects)) return; CollectionUtils.filter(
creativeIndexObjects,
creative -> {
//审核状态必须是通过
return creative.getAuditStatus().equals(CommonStatus.VALID.getStatus())
&& creative.getWidth().equals(width)
&& creative.getHeight().equals(height)
&& type.contains(creative.getType());
}
);
}
  • 组建搜索返回对象

    正常业务场景中,同一个广告位可以展示多个广告信息,也可以只展示一个广告信息,这个需要根据具体的业务场景来做不同的处理,本次为了演示方便,会从返回的创意列表中随机选择一个创意广告信息进行展示,当然大家也可以根据业务类型,设置不同的优先级或者权重值来进行广告选择。
/**
* 从创意列表中随机获取一条创意广告返回出去
*
* @param creativeIndexObjects 创意广告list
*/
private List<SearchResponse.Creative> buildCreativeResponse(List<CreativeIndexObject> creativeIndexObjects) {
if (CollectionUtils.isEmpty(creativeIndexObjects)) return Collections.EMPTY_LIST; //随机获取一个广告创意,也可以实现优先级排序,也可以根据权重值等等,具体根据业务
CreativeIndexObject randomObject = creativeIndexObjects.get(
Math.abs(new Random().nextInt()) % creativeIndexObjects.size()
);
//List<SearchResponse.Creative> result = new ArrayList<>();
//result.add(SearchResponse.convert(randomObject)); return Collections.singletonList(
SearchResponse.convert(randomObject)
);
}

完整的请求过滤实现方法:

@Service
@Slf4j
public class SearchImpl implements ISearch {
@Override
public SearchResponse fetchAds(SearchRequest request) { //获取请求广告位信息
List<AdSlot> adSlotList = request.getRequestInfo().getAdSlots(); //获取三个Feature信息
KeywordFeature keywordFeature = request.getFeatureInfo().getKeywordFeature();
HobbyFeatrue hobbyFeatrue = request.getFeatureInfo().getHobbyFeatrue();
DistrictFeature districtFeature = request.getFeatureInfo().getDistrictFeature();
//Feature关系
FeatureRelation featureRelation = request.getFeatureInfo().getRelation(); //构造响应对象
SearchResponse response = new SearchResponse();
Map<String, List<SearchResponse.Creative>> adSlotRelationAds = response.getAdSlotRelationAds(); for (AdSlot adSlot : adSlotList) {
Set<Long> targetUnitIdSet;
//根据流量类型从缓存中获取 初始 广告信息
Set<Long> adUnitIdSet = IndexDataTableUtils.of(
AdUnitIndexAwareImpl.class
).match(adSlot.getPositionType()); // 根据三个维度过滤
if (featureRelation == FeatureRelation.AND) {
filterKeywordFeature(adUnitIdSet, keywordFeature);
filterHobbyFeature(adUnitIdSet, hobbyFeatrue);
filterDistrictFeature(adUnitIdSet, districtFeature); targetUnitIdSet = adUnitIdSet;
} else {
targetUnitIdSet = getOrRelationUnitIds(adUnitIdSet, keywordFeature, hobbyFeatrue, districtFeature);
}
//获取 推广计划 对象list
List<AdUnitIndexObject> unitIndexObjects = IndexDataTableUtils.of(AdUnitIndexAwareImpl.class)
.fetch(targetUnitIdSet);
//根据状态过滤数据
filterAdUnitAndPlanStatus(unitIndexObjects, CommonStatus.VALID); //获取 推广创意 id list
List<Long> creativeIds = IndexDataTableUtils.of(CreativeRelationUnitIndexAwareImpl.class)
.selectAdCreativeIds(unitIndexObjects);
//根据 推广创意ids获取推广创意
List<CreativeIndexObject> creativeIndexObjects = IndexDataTableUtils.of(CreativeIndexAwareImpl.class)
.fetch(creativeIds); //根据 广告位adslot 实现对创意数据的过滤
filterCreativeByAdSlot(creativeIndexObjects, adSlot.getWidth(), adSlot.getHeight(), adSlot.getType()); //一个广告位可以展示多个广告,也可以仅展示一个广告,具体根据业务来定
adSlotRelationAds.put(
adSlot.getAdSlotCode(),
buildCreativeResponse(creativeIndexObjects)
);
} return response;
}
...
检索服务对外提供
  • 暴露API接口

    上文中,我们实现了检索服务的核心逻辑,接下来,我们需要对外暴露我们的广告检索服务接口,在SearchController中提供:

        @PostMapping("/fetchAd")
    public SearchResponse fetchAdCreative(@RequestBody SearchRequest request) {
    log.info("ad-serach: fetchAd ->{}", JSON.toJSONString(request));
    return search.fetchAds(request);
    }
  • 实现API网关配置

    zuul:
    routes:
    sponsor: #在路由中自定义服务路由名称
    path: /ad-sponsor/**
    serviceId: mscx-ad-sponsor #微服务name
    strip-prefix: false
    search: #在路由中自定义服务路由名称
    path: /ad-search/**
    serviceId: mscx-ad-search #微服务name
    strip-prefix: false
    prefix: /gateway/api
    strip-prefix: true #不对 prefix: /gateway/api 设置的路径进行截取,默认转发会截取掉配置的前缀

[Spring cloud 一步步实现广告系统] 18. 查询返回广告创意的更多相关文章

  1. [Spring cloud 一步步实现广告系统] 19. 监控Hystrix Dashboard

    在之前的18次文章中,我们实现了广告系统的广告投放,广告检索业务功能,中间使用到了 服务发现Eureka,服务调用Feign,网关路由Zuul以及错误熔断Hystrix等Spring Cloud组件. ...

  2. [Spring cloud 一步步实现广告系统] 21. 系统错误汇总

    广告系统学习过程中问题答疑 博客园 Eureka集群启动报错 Answer 因为Eureka在集群启动过程中,会连接集群中其他的机器进行数据同步,在这个过程中,如果别的服务还没有启动完成,就会出现Co ...

  3. [Spring cloud 一步步实现广告系统] 2. 配置&Eureka服务

    父项目管理 首先,我们在创建投放系统之前,先看一下我们的工程结构: mscx-ad-sponsor就是我们的广告投放系统.如上结构,我们需要首先创建一个Parent Project mscx-ad 来 ...

  4. [Spring cloud 一步步实现广告系统] 22. 广告系统回顾总结

    到目前为止,我们整个初级广告检索系统就初步开发完成了,我们来整体回顾一下我们的广告系统. 整个广告系统编码结构如下: mscx-ad 父模块 主要是为了方便我们项目的统一管理 mscx-ad-db 这 ...

  5. [Spring cloud 一步步实现广告系统] 7. 中期总结回顾

    在前面的过程中,我们创建了4个project: 服务发现 我们使用Eureka 作为服务发现组件,学习了Eureka Server,Eureka Client的使用. Eureka Server 加依 ...

  6. [Spring cloud 一步步实现广告系统] 1. 业务架构分析

    什么是广告系统? 主要包含: 广告主投放广告的<广告投放系统> 媒体方(广告展示媒介-)检索广告用的<广告检索系统> 广告计费系统(按次,曝光量等等) 报表系统 Etc. 使用 ...

  7. [Spring cloud 一步步实现广告系统] 13. 索引服务编码实现

    上一节我们分析了广告索引的维护有2种,全量索引加载和增量索引维护.因为广告检索是广告系统中最为重要的环节,大家一定要认真理解我们索引设计的思路,接下来我们来编码实现索引维护功能. 我们来定义一个接口, ...

  8. [Spring cloud 一步步实现广告系统] 12. 广告索引介绍

    索引设计介绍 在我们广告系统中,为了我们能更快的拿到我们想要的广告数据,我们需要对广告数据添加类似于数据库index一样的索引结构,分两大类:正向索引和倒排索引. 正向索引 通过唯一键/主键生成与对象 ...

  9. [Spring cloud 一步步实现广告系统] 11. 使用Feign实现微服务调用

    上一节我们使用了Ribbon(基于Http/Tcp)进行微服务的调用,Ribbon的调用比较简单,通过Ribbon组件对请求的服务进行拦截,通过Eureka Server 获取到服务实例的IP:Por ...

随机推荐

  1. .Net高级编程-自定义错误页 web.config中<customErrors>节点配置

    错误页 1.当页面发生错误的时候,ASP.Net会将错误信息展示出来(Sqlconnection的错误就能暴露连接字符串),这样一来不好看,二来泄露网站的内部实现信息,给网站带来安全隐患,因此需要定制 ...

  2. web前端兼容性问题总结

    1.   HTML对象获取问题 FireFox:document.getElementById("idName");ie:document.idname或者document.get ...

  3. Linux 安装 lanmp

    Lanmp介绍 lanmp一键安装包是wdlinux官网2010年底开始推出的web应用环境的快速简易安装包. 执行一个脚本,整个环境就安装完成就可使用,快速,方便易用,安全稳定 lanmp一键安装包 ...

  4. tomcat问题解决

    tomcat问题解决 运行tomcat环境下,idea中出现 error running 项目名address localhost1099 is already in use 的时候,如何解决? 1, ...

  5. java三大集合遍历

    1. 场景描述 今天需要用到map集合遍历,一下子忘记咋写了,以前一般用map.get()直接获取值,很少遍历map,刚好总结下java中常用的几个集合-map,set,list遍历. 2. 解决方案 ...

  6. 多线程总结-同步之volatile关键字

    目录 1 案例引出可见性 2 案例引出原子性 1 案例引出可见性 代码解析:新起一个子线程执行m()方法,1秒后主线程将b置为false,子线程是否会停止执行死循环while(b){},打印" ...

  7. C语言指针专题——为何要学习指针

    欢迎转发本文! 之前的文章与各位谈论了指针是什么,以及指针为何这那么难学.不少知友留言说看了我的文章对指针了解了不少,这给我继续创作提供了莫大的动力啊.指针其实就是一个纸老虎,你看着可怕,等你了解其本 ...

  8. Spring Cloud Alibaba | Sentinel: 分布式系统的流量防卫兵初探

    目录 Spring Cloud Alibaba | Sentinel: 分布式系统的流量防卫兵初探 1. Sentinel 是什么? 2. Sentinel 的特征: 3. Sentinel 的开源生 ...

  9. 【Netty】Netty简介及服务器客户端简单开发流程

    什么是Netty Netty是一个基于Java NIO的编写客服端服务器的框架,是一个异步事件框架. 官网https://netty.io/ 为什么选择Netty 由于JAVA NIO编写服务器的过程 ...

  10. 【DFS的分支限界】(例题-算式等式)

    不知道DFS的请滚去 这里瞅一眼再说. -分支限界- 基本概念: 类似于回溯法,也是一种在问题的解空间树T上搜索问题解的算法.但在一般情况下,分支限界法与回溯法的求解目标不同.回溯法的求解目标是找出T ...