首页 文章 精选 留言 我的

精选列表

搜索[Spring Framework],共10000篇文章
优秀的个人博客,低调大师

Spring注解驱动开发之六——@Bean指定初始化、销毁方法、BeanPostProcessor后置处理器

本文包含以下内容: 新、旧方法指定初始化和销毁方法 InitializingBean、DisposableBean指定生命周期方法 @PostConstruct、@PreDestroy调用Bean 创建前后的方法 BeanPostProcessor 后置处理器,进行初始化 前后的工作 上述3种的联合调试 1.新、旧方法指定初始化和销毁方法 在之前,指定一个Bean 的初始化方法和销毁方法可以在 bean.xml 文件中<bean> 标签中指定 init-method和 destroy-method 进行初始函数、销毁函数的指定。要求没有任何参数、可以报任何异常 在注解中也可以通过在 @Bean (initMethod= "init" ,destroyMethod= "detory" ) 注解中指定 ,初始化方法和销毁方法。下面开始测试 1.建立实体类 @Componentpublic class Car { public Car(){ System.out.println("car constructor..."); } public void init(){ System.out.println("car ... init..."); } public void detory(){ System.out.println("car ... detory..."); }} 2.通过注解标记需要 创建的类 @Bean(initMethod="init",destroyMethod="detory") public Car car(){ return new Car(); } 3.编写测试方法进行 测试 @Test public void test01(){ //1、创建ioc容器 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class); System.out.println("容器创建完成..."); //applicationContext.getBean("car"); //关闭容器 applicationContext.close(); } 4.获得结果 5.测试, 非单例的情况加上 @Scope ( "prototype" ) 注解,获得结果,表示,非单例的组件在容器创建后就不在进行管理,不再进行销毁操作了 2.InitializingBean、DisposableBean指定生命周期方法 除了通过指定指定初始化方法、销毁方法外,还可以通过继承 InitializingBean 、 DisposableBean 接口分别实现其afterPropertiesSet 、destroy 方法实现,在Bean创建前后执行函数操作,下面进行测试: 1)建立实体类 @Componentpublic class Cat implements InitializingBean,DisposableBean { public Cat(){ System.out.println("cat constructor..."); } @Override public void destroy() throws Exception { // TODO Auto-generated method stub System.out.println("cat...destroy..."); } @Override public void afterPropertiesSet() throws Exception { // TODO Auto-generated method stub System.out.println("cat...afterPropertiesSet..."); }} 2)运行获得结果 3.@PostConstruct、@PreDestroy调用Bean 创建前后的方法 在springboot中可以通过@PostConstruct、@PreDestroy,分别制定在Bean装配前后执行的函数。下面开始测试: 1)建立实体类 @Componentpublic class Dog implements ApplicationContextAware { //@Autowired private ApplicationContext applicationContext; public Dog(){ System.out.println("dog constructor..."); } //对象创建并赋值之后调用 @PostConstruct public void init(){ System.out.println("Dog....@PostConstruct..."); } //容器移除对象之前 @PreDestroy public void detory(){ System.out.println("Dog....@PreDestroy..."); }} 2)运行测试方法,在Bean创建完成之后、容器创建完成前 调用的 @PostConstruct 在Bean销毁前调用了 @PreDestroy的方法 4.BeanPostProcessor 后置处理器,进行初始化 前后的工作 可以通过建立BeanPostProcessor 实现类,通过其 postProcessBeforeInitialization 、postProcessAfterInitialization方法可以在Bean 完成创建之后、Bean 销毁之前,执行指定方法。下面开始测试: 1.创建自定义 BeanPostProcessor 实现类 /** * 后置处理器:初始化前后进行处理工作 * 将后置处理器加入到容器中 * @author lfy */@Componentpublic class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { // TODO Auto-generated method stub System.out.println("postProcessBeforeInitialization..."+beanName+"=>"+bean); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { // TODO Auto-generated method stub System.out.println("postProcessAfterInitialization..."+beanName+"=>"+bean); return bean; }} 2.运行测试,得到结果,在Bean创建完成之后,调用了 postProcessBeforeInitialization方法 ,在创建Bean完成之后调用 postProcessAfterInitialization 。结果如下: 5.上述3种的联合调试 综合上述内容,同时加入(1)InitializingBean、DisposableBean (2)@PostConstruct、@PreDestroy (3)BeanPostProcessor查看执行方法的先后顺序 1.)首先是创建,测试结果如下图所示:执行顺序是,现执行 postProcessBeforeInitialization而后是构造方法 、而后是 postProcessAfterInitialization而后是 InitializingBean的 afterPropertiesSet -END- 可以关注我的公众号,免费获取价值1980元学习资料 点击“在看”,学多少都不会忘~ 本文分享自微信公众号 - 阿聪的全栈之路(gh_ffab7c84fb0c)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

web-flash 1.2 发布,基于 Spring Boot&Vue.js 的前后端分离后台管理系统

web-flash 发布将近半年,系统已经趋于稳定,本次没有重大功能更新,主要进行性能的优化和部分组件的例行升级以及bug修复 本次更新内容如下: Change 删除角色时判断是有用户使用该角色,如果有则不允许删除 Change 简化手机站点api地址配置 Change baseService增加缓存功能 Update 升级element-ui至2.11.0 Update 升级Vue全家桶:vue.js(2.6.10),vuex(3.1.1),vue-router(3.0.3),axios(0.18.1),"vue-loader(15.7.0),vue-template-compiler(2.6.10) Change 完善在线文档 Fix bug 本次发布版地址:https://gitee.com/enilu/web-flash/releases/1.2

优秀的个人博客,低调大师

[Spring cloud 一步步实现广告系统] 18. 查询返回广告创意

根据三个维度继续过滤 在上一节中我们实现了根据流量信息过滤的代码,但是我们的条件有可能是多条件一起传给我们的检索服务的,本节我们继续实现根据推广单元的三个维度条件的过滤。 在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 一步步实现广告系统] 17. 根据流量类型查询广告

广告检索服务 功能介绍 媒体方(手机APP打开的展示广告,走在路上看到的大屏幕广告等等) 请求数据对象实现 从上图我们可以看出,在媒体方向我们的广告检索系统发起请求的时候,请求中会有很多的请求参数信息,他们分为了三个部分,我们来编码封装这几个参数对象信息以及我们请求本身的信息。Let's code. 创建广告检索请求接口 /** * ISearch for 请求接口, * 根据广告请求对象,获取广告响应信息 * * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a> */ @FunctionalInterface public interface ISearch { /** * 根据请求返回广告结果 */ SearchResponse fetchAds(SearchRequest request); } 创建SearchRequest,包含三部分:mediaId,RequestInfo,FeatureInfo @Data @NoArgsConstructor @AllArgsConstructor public class SearchRequest { //媒体方请求标示 private String mediaId; //请求基本信息 private RequestInfo requestInfo; //匹配信息 private FeatureInfo featureInfo; @Data @NoArgsConstructor @AllArgsConstructor public static class RequestInfo { 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 HobbyFeatrue hobbyFeatrue; private FeatureRelation relation = FeatureRelation.AND; } } 其他的对象大家可以去github传送门 & gitee传送门 下载源码。 检索响应对象定义 /** * SearchResponse for 检索API响应对象 * * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a> */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class SearchResponse { //一个广告位,可以展示多个广告 //Map key为广告位 AdSlot#adSlotCode public Map<String, List<Creative>> adSlotRelationAds = new HashMap<>(); @Data @Builder @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.life-runner.com", "www.babydy.cn"); //点击监控url private List<String> clickMonitorUrl = Arrays.asList("www.life-runner.com", "www.babydy.cn"); } /** * 我们的检索服务针对的是内存中的索引检索,那么我们就需要一个转换方法 */ public static Creative convert(CreativeIndexObject object) { return Creative.builder() .adId(object.getAdId()) .adUrl(object.getAdUrl()) .width(object.getWidth()) .height(object.getHeight()) .type(object.getType()) .materialType(object.getMaterialType()) .build(); } } 根据流量类型广告过滤 流量类型本身属于推广单元下的类目,有很多种类贴片广告,开屏广告等等,这些类型需要同步到媒体方,媒体方会根据不同的流量类型发起不同的广告请求,我们需要先定义一个流量类型的信息类。 public class AdUnitConstants { public static class PositionType{ //App启动时展示的、展示时间短暂的全屏化广告形式。 private static final int KAIPING = 1; //电影开始之前的广告 private static final int TIEPIAN = 2; //电影播放中途广告 private static final int TIEPIAN_MIDDLE = 4; //暂停视频时候播放的广告 private static final int TIEPIAN_PAUSE = 8; //视频播放完 private static final int TIEPIAN_POST = 16; } } 从上述类型的数字,我们可以看出是2的倍数,这是为了使用位运算提升性能。 在com.sxzhongf.ad.index.adunit.AdUnitIndexObject中,我们添加类型校验方法: public static boolean isAdSlotType(int adSlotType, int positionType) { switch (adSlotType) { case AdUnitConstants.PositionType.KAIPING: return isKaiPing(positionType); case AdUnitConstants.PositionType.TIEPIAN: return isTiePian(positionType); case AdUnitConstants.PositionType.TIEPIAN_MIDDLE: return isTiePianMiddle(positionType); case AdUnitConstants.PositionType.TIEPIAN_PAUSE: return isTiePianPause(positionType); case AdUnitConstants.PositionType.TIEPIAN_POST: return isTiePianPost(positionType); default: return false; } } /** * 与运算,低位取等,高位补零。 * 如果 > 0,则为开屏 */ private static boolean isKaiPing(int positionType) { return (positionType & AdUnitConstants.PositionType.KAIPING) > 0; } private static boolean isTiePianMiddle(int positionType) { return (positionType & AdUnitConstants.PositionType.TIEPIAN_MIDDLE) > 0; } private static boolean isTiePianPause(int positionType) { return (positionType & AdUnitConstants.PositionType.TIEPIAN_PAUSE) > 0; } private static boolean isTiePianPost(int positionType) { return (positionType & AdUnitConstants.PositionType.TIEPIAN_POST) > 0; } private static boolean isTiePian(int positionType) { return (positionType & AdUnitConstants.PositionType.TIEPIAN) > 0; } 无所如何,我们都是需要根据positionType进行数据查询过滤,我们在之前的com.sxzhongf.ad.index.adunit.AdUnitIndexAwareImpl中添加2个方法来实现过滤: /** * 过滤当前是否存在满足positionType的UnitIds */ public Set<Long> match(Integer positionType) { Set<Long> adUnitIds = new HashSet<>(); objectMap.forEach((k, v) -> { if (AdUnitIndexObject.isAdSlotType(positionType, v.getPositionType())) { adUnitIds.add(k); } }); return adUnitIds; } /** * 根据UnitIds查询AdUnit list */ public List<AdUnitIndexObject> fetch(Collection<Long> adUnitIds) { if (CollectionUtils.isEmpty(adUnitIds)) { return Collections.EMPTY_LIST; } List<AdUnitIndexObject> result = new ArrayList<>(); adUnitIds.forEach(id -> { AdUnitIndexObject object = get(id); if (null == object) { log.error("AdUnitIndexObject does not found:{}", id); return; } result.add(object); }); return result; } 实现Search服务接口 上述我们准备了一系列的查询方法,都是为了根据流量类型查询广告单元信息,我们现在开始实现我们的查询接口,查询接口中,我们可以获取到媒体方的请求对象信息,它带有一系列查询所需要的过滤参数: /** * SearchImpl for 实现search 服务 * * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a> */ @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()); } return null; } }

优秀的个人博客,低调大师

[Spring cloud 一步步实现广告系统] 14. 全量索引代码实现

上一节我们实现了索引基本操作的类以及索引缓存工具类,本小节我们开始实现加载全量索引数据,在加载全量索引数据之前,我们需要先将数据库中的表数据导出到一份文件中。Let's code. 1.首先定义一个常量类,用来存储导出文件存储的目录和文件名称 因为我们导出的文件需要在搜索服务中使用到,因此,我们将文件名 & 目录以及导出对象的信息编写在mscx-ad-commom项目中。 public class FileConstant { public static final String DATA_ROOT_DIR = "/Users/xxx/Documents/promotion/data/mysql/"; //各个表数据的存储文件名 public static final String AD_PLAN = "ad_plan.data"; public static final String AD_UNIT = "ad_unit.data"; public static final String AD_CREATIVE = "ad_creative.data"; public static final String AD_CREATIVE_RELARION_UNIT = "ad_creative_relation_unit.data"; public static final String AD_UNIT_HOBBY = "ad_unit_hobby.data"; public static final String AD_UNIT_DISTRICT = "ad_unit_district.data"; public static final String AD_UNIT_KEYWORD = "ad_unit_keyword.data"; } 2.定义索引对象导出的字段信息,依然用Ad_Plan为例。 /** * AdPlanTable for 需要导出的表字段信息 => 是搜索索引字段一一对应 * * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a> */ @Data @AllArgsConstructor @NoArgsConstructor public class AdPlanTable { private Long planId; private Long userId; private Integer planStatus; private Date startDate; private Date endDate; } 3.导出文件服务实现 同样,最好的实现方式就是将导出服务作为一个子工程来独立运行,我这里直接实现在了mscx-ad-db项目中 定义一个空接口,为了符合我们的编码规范 /** * IExportDataService for 导出数据库广告索引初始化数据 * * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a> */ public interface IExportDataService { } 实现service @Slf4j @Service public class ExportDataServiceImpl implements IExportDataService { @Autowired private AdPlanRepository planRepository; /** * 导出 {@code AdPlan} from DB to File * * @param fileName 文件名称 */ public void exportAdPlanTable(String fileName) { List<AdPlan> planList = planRepository.findAllByPlanStatus(CommonStatus.VALID.getStatus()); if (CollectionUtils.isEmpty(planList)) { return; } List<AdPlanTable> planTables = new ArrayList<>(); planList.forEach(item -> planTables.add( new AdPlanTable( item.getPlanId(), item.getUserId(), item.getPlanStatus(), item.getStartDate(), item.getEndDate() ) )); //将数据写入文件 Path path = Paths.get(fileName); try (BufferedWriter writer = Files.newBufferedWriter(path)) { for (AdPlanTable adPlanTable : planTables) { writer.write(JSON.toJSONString(adPlanTable)); writer.newLine(); } writer.close(); } catch (IOException e) { e.printStackTrace(); log.error("export AdPlanTable Exception!"); } } } 实现Controller,提供操作入口 @Slf4j @Controller @RequestMapping("/export") public class ExportDataController { private final ExportDataServiceImpl exportDataService; @Autowired public ExportDataController(ExportDataServiceImpl exportDataService) { this.exportDataService = exportDataService; } @GetMapping("/export-plan") public CommonResponse exportAdPlans() { exportDataService.exportAdPlanTable(String.format("%s%s", FileConstant.DATA_ROOT_DIR, FileConstant.AD_PLAN)); return new CommonResponse(); } } 结果文件内容如下,每一行都代表了一个推广计划 {"endDate":1561438800000,"planId":10,"planStatus":1,"startDate":1561438800000,"userId":10} {"endDate":1561438800000,"planId":11,"planStatus":1,"startDate":1561438800000,"userId":10} 根据文件内容构建索引 我们在之前编写索引服务的时候,创建了一些索引需要使用的实体对象类,比如构建推广计划索引的时候,需要使用到的实体对象com.sxzhongf.ad.index.adplan.AdPlanIndexObject,可是呢,我们在上一节实现索引导出的时候,实体对象又是common 包中的com.sxzhongf.ad.common.export.table.AdPlanTable,读取出来文件中的数据只能反序列化为JSON.parseObject(p, AdPlanTable.class),我们需要将2个对象做相互映射才能创建索引信息。 1.首先我们定义一个操作类型枚举,代表我们每一次的操作类型(也需要对应到后期binlog监听的操作类型) public enum OperationTypeEnum { ADD, UPDATE, DELETE, OTHER; public static OperationTypeEnum convert(EventType type) { switch (type) { case EXT_WRITE_ROWS: return ADD; case EXT_UPDATE_ROWS: return UPDATE; case EXT_DELETE_ROWS: return DELETE; default: return OTHER; } } } 2.因为全量索引的加载和增量索引加载的本质是一样的,全量索引其实就是一种特殊的增量索引,为了代码的可复用,我们创建统一的类来操作索引。 /** * AdLevelDataHandler for 通用处理索引类 * 1. 索引之间存在层级划分,也就是相互之间拥有依赖关系的划分 * 2. 加载全量索引其实是增量索引 "添加"的一种特殊实现 * * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a> */ @Slf4j public class AdLevelDataHandler { /** * 实现广告推广计划的第二层级索引实现。 * (第一级为用户层级,但是用户层级不参与索引,所以从level 2开始) * 第二层级的索引是表示 不依赖于其他索引,但是可被其他索引所依赖 */ public static void handleLevel2Index(AdPlanTable adPlanTable, OperationTypeEnum type) { // 对象转换 AdPlanIndexObject planIndexObject = new AdPlanIndexObject( adPlanTable.getPlanId(), adPlanTable.getUserId(), adPlanTable.getPlanStatus(), adPlanTable.getStartDate(), adPlanTable.getEndDate() ); //调用通用方法处理,使用IndexDataTableUtils#of来获取索引的实现类bean handleBinlogEvent( // 在前一节我们实现了一个索引工具类,来获取注入的bean对象 IndexDataTableUtils.of(AdPlanIndexAwareImpl.class), planIndexObject.getPlanId(), planIndexObject, type ); } /** * 处理全量索引和增量索引的通用处理方式 * K,V代表索引的键和值 * * @param index 索引实现代理类父级 * @param key 键 * @param value 值 * @param type 操作类型 */ private static <K, V> void handleBinlogEvent(IIndexAware<K, V> index, K key, V value, OperationTypeEnum type) { switch (type) { case ADD: index.add(key, value); break; case UPDATE: index.update(key, value); break; case DELETE: index.delete(key, value); break; default: break; } } } 3.读取文件实现全量索引加载。 因为我们文件加载之前需要依赖另一个组件,也就是我们的索引工具类,需要添加上@DependsOn("indexDataTableUtils"),全量索引在系统启动的时候就需要加载,我们需要添加@PostConstruct来实现初始化加载,被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器调用一次。 @Component @DependsOn("indexDataTableUtils") public class IndexFileLoader { /** * 服务启动时,执行全量索引加载 */ @PostConstruct public void init() { //加载 推广计划 List<String> adPlanStrings = loadExportedData(String.format("%s%s", FileConstant.DATA_ROOT_DIR, FileConstant.AD_PLAN )); adPlanStrings.forEach(p -> AdLevelDataHandler.handleLevel2Index( JSON.parseObject(p, AdPlanTable.class), OperationTypeEnum.ADD )); } /** * <h3>读取全量索引加载需要的文件</h3> * * @param fileName 文件名称 * @return 文件行数据 */ private List<String> loadExportedData(String fileName) { try (BufferedReader reader = Files.newBufferedReader(Paths.get(fileName))) { return reader.lines().collect(Collectors.toList()); } catch (IOException e) { throw new RuntimeException(e.getMessage()); } } } Tips 在实现初始化加载全量索引的过程中,一定要保证数据加载的顺序问题,因为不同的数据有可能存在着相互依赖的关联关系,一旦顺序写错,会造成程序报错问题。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

用户登录
用户注册