促销活动核心逻辑说明:缓存设计详细解析

后台-系统设置-扩展变量-手机广告位-内容正文顶部

缓存设计

1.设计思路

  在当前版本程序中,我们使用了促销脚本引擎的相关方式来实现了促销活动核心价格的计算,具体可参考《促销活动脚本引擎生成架构》这篇文档。

  简单来讲,就是在发布促销活动时,我们将促销活动相关信息(包括促销活动本身信息,如活动ID、活动类型等;还包括直接可调用的JavaScript语言方法,这些方法包含验证活动是否有效、计算出商品参与此活动后的价格等)存放在Redis缓存中,当用户购买了参与促销活动的商品后,程序不会再去数据库中读取相应的数据,而是直接在Redis缓存中拿到数据直接进行价格的计算。

2.实现方法

  那我们是如何将促销脚本相关信息放入缓存中的呢?

  在程序中,大部分促销活动都是有开始时间和结束时间的,并且活动在生效之后不允许删除和修改,根据这个特性,我们如果在促销活动创建成功的时候就生成促销脚本,那么如果促销活动还未生效的情况下,就需要考虑在修改和删除时还要对已经生成的促销脚本进行更新和删除操作,所以在生成促销脚本时,我们采用了延时任务执行器

  所谓延时任务执行器,是我们利用Rabbitmq延迟队列来实现的定时任务,就是将消息设置一个过期时间,放入一个没有读取的队列中,让消息过期后自动转入另外一个队列中,监控这个队列消息的监听处来处理定时任务具体的操作。具体操作流程大致如下:

  如上所述,我们在发布促销活动时,只需要把创建促销脚本的消息以活动开始时间为过期时间放入过期队列中,当活动生效时,这条过期消息会转发到正常消息队列中,然后被对应的消费者执行并创建促销脚本引擎信息。

3.接口调用流程

  TimeTriggerExecuter接口实现类:

4.相关代码展示

  延时任务接口实现类(RabbitmqTimeTrigger)—以添加延时任务为例:

  /**

  *添加延时任务

  *

  *@paramexecuterName执行器

  *@paramparam执行参数

  *@paramtriggerTime执行时间

  *@paramuniqueKey如果是一个需要有修改/取消延时任务功能的延时任务,

  *请填写此参数,作为后续删除,修改做为唯一凭证

  *建议参数为:PINTUAZN_{ACTIVITY_ID}例如pintuan_123

  *业务内全局唯一

  */

  @Override

  publicvoidadd(StringexecuterName,Objectparam,LongtriggerTime,StringuniqueKey){

  if(StringUtil.isEmpty(uniqueKey)){

  uniqueKey=StringUtil.getRandStr(10);

  }

  //标识任务需要执行

  cache.put(RabbitmqTriggerUtil.generate(executerName,triggerTime,uniqueKey),1);

  TimeTriggerMsgtimeTriggerMsg=newTimeTriggerMsg(executerName,param,triggerTime,uniqueKey);

  logger.debug("定时执行在【"+DateUtil.toString(triggerTime,"yyyy-MM-ddHH:mm:ss")+"】,消费【"+param.toString()+"】");

  rabbitTemplate.convertAndSend(TimeTriggerConfig.DELAYED_EXCHANGE_XDELAY,TimeTriggerConfig.DELAY_ROUTING_KEY_XDELAY,timeTriggerMsg,message->{

  Longcurrent=DateUtil.getDateline();

  //如果执行的延时任务应该是在现在日期之前执行的,那么补救一下,要求系统一秒钟后执行

  if(triggerTime

  message.getMessageProperties().setDelay(1000);

  }else{

  Longtime=(triggerTime-current)*1000+5000;

  message.getMessageProperties().setHeader("x-delay",time);

  }

  logger.debug("还有【"+message.getMessageProperties().getExpiration()+"】执行任务");

  returnmessage;

  });

  }

  监听并接收延时任务消息的接收者(TimeTriggerConsumer):

  /**

  *接收消息,监听CONSUMPTION_QUEUE队列

  */

  @RabbitListener(queues=TimeTriggerConfig.IMMEDIATE_QUEUE_XDELAY)

  publicvoidconsume(TimeTriggerMsgtimeTriggerMsg){

  try{

  Stringkey=RabbitmqTriggerUtil.generate(timeTriggerMsg.getTriggerExecuter(),timeTriggerMsg.getTriggerTime(),timeTriggerMsg.getUniqueKey());

  //如果这个任务被标识不执行

  if(cache.get(key)==null){

  logger.debug("执行器执行被取消:"+timeTriggerMsg.getTriggerExecuter()+"|任务标识:"+timeTriggerMsg.getUniqueKey());

  return;

  }

  logger.debug("执行器执行:"+timeTriggerMsg.getTriggerExecuter());

  logger.debug("执行器参数:"+JsonUtil.objectToJson(timeTriggerMsg.getParam()));

  //执行任务前清除标识

  cache.remove(key);

  TimeTriggerExecutertimeTriggerExecuter=(TimeTriggerExecuter)ApplicationContextHolder.getBean(timeTriggerMsg.getTriggerExecuter());

  timeTriggerExecuter.execute(timeTriggerMsg.getParam());

  }catch(Exceptione){

  logger.error("延时任务异常:",e);

  }

  }

  延时任务消息的消费者—共有多个实现,这里以优惠券脚本生成和删除消费者(CouponScriptTimeTriggerExecuter)为例:

  @Override

  publicvoidexecute(Objectobject){

  PromotionScriptMsgpromotionScriptMsg=(PromotionScriptMsg)object;

  //获取促销活动ID

  LongpromotionId=promotionScriptMsg.getPromotionId();

  //优惠券级别缓存key

  StringcacheKey=CachePrefix.COUPON_PROMOTION.getPrefix()+promotionId;

  //如果是优惠券开始生效

  if(ScriptOperationTypeEnum.CREATE.equals(promotionScriptMsg.getOperationType())){

  //获取优惠券详情

  CouponDOcoupon=this.couponClient.getModel(promotionId);

  //渲染并读取优惠券脚本信息

  Stringscript=renderCouponScript(coupon);

  //将优惠券脚本信息放入缓存

  cache.put(cacheKey,script);

  //优惠券生效后,立马设置一个优惠券失效的流程

  promotionScriptMsg.setOperationType(ScriptOperationTypeEnum.DELETE);

  StringuniqueKey="{TIME_TRIGGER_"+promotionScriptMsg.getPromotionType().name()+"}_"+promotionId;

  timeTrigger.add(TimeExecute.COUPON_SCRIPT_EXECUTER,promotionScriptMsg,promotionScriptMsg.getEndTime(),uniqueKey);

  this.logger.debug("优惠券["+promotionScriptMsg.getPromotionName()+"]开始生效,id=["+promotionId+"]");

  }else{

  //删除缓存中的促销脚本数据

  cache.remove(cacheKey);

  this.logger.debug("优惠券["+promotionScriptMsg.getPromotionName()+"]已经失效,id=["+promotionId+"]");

  }

  }

  /**

  *渲染并读取优惠券脚本信息

  *@paramcoupon优惠券信息

  *@return

  */

  privateStringrenderCouponScript(CouponDOcoupon){

  Mapmodel=newHashMap<>();

  Mapparams=newHashMap<>();

  params.put("startTime",coupon.getStartTime().toString());

  params.put("endTime",coupon.getEndTime().toString());

  params.put("couponPrice",coupon.getCouponPrice());

  model.put("coupon",params);

  Stringpath="coupon.ftl";

  Stringscript=ScriptUtil.renderScript(path,model);

  logger.debug("生成优惠券脚本:"+script);

  returnscript;

  }

5.脚本渲染和执行工具类--ScriptUtil

  渲染脚本方法:

  /**

  *渲染并读取脚本内容

  *@paramname脚本模板名称(例:test.js,test.html,test.ftl等)

  *@parammodel渲染脚本需要的数据内容

  *@return

  */

  publicstaticStringrenderScript(Stringname,Mapmodel){

  StringWriterstringWriter=newStringWriter();

  try{

  Configurationcfg=newConfiguration(Configuration.VERSION_2_3_28);

  cfg.setClassLoaderForTemplateLoading(Thread.currentThread().getContextClassLoader(),"/script_tpl");

  cfg.setDefaultEncoding("UTF-8");

  cfg.setNumberFormat("#.##");

  Templatetemp=cfg.getTemplate(name);

  temp.process(model,stringWriter);

  stringWriter.flush();

  returnstringWriter.toString();

  }catch(Exceptione){

  log.error(e.getMessage());

  }finally{

  try{

  stringWriter.close();

  }catch(IOExceptionex){

  log.error(ex.getMessage());

  }

  }

  returnnull;

  }

  执行脚本方法:

  /**
  *@Description:执行script脚本
  *@parammethodscript方法名
  *@paramparams参数
  *@paramscript脚本
  *@return:返回执行结果
  *@Author:liuyulei
  *@Date:2020/1/7
  */
  publicstaticObjectexecuteScript(Stringmethod,Map<String,Object>params,Stringscript){
  if(StringUtil.isEmpty(script)){
  log.debug("scriptis"+script);
  returnnewObject();
  }
  
  try{
  ScriptEngineManagermanager=newScriptEngineManager();
  ScriptEngineengine=manager.getEngineByName("javascript");

  log.debug("脚本参数:");
  for(Stringkey:params.keySet()){
  log.debug(key+"="+params.get(key));
  engine.put(key,params.get(key));
  }
  
  engine.eval(script);
  log.debug("script脚本:");
  log.debug(script);
  
  Invocableinvocable=(Invocable)engine;
  
  returninvocable.invokeFunction(method);
  }catch(ScriptExceptione){
  log.error(e.getMessage(),e);
  }catch(NoSuchMethodExceptione){
  log.error(e.getMessage(),e);
  }
  returnnewObject();
  }
  ```

  以上就是易族智汇javashop为您整理的关于技术文档的内容,就为您介绍到这里。想了解更多关于技术文档的内容,请关注“技术文档栏目https://www.javamall.com.cn/xueyuan/jswd/”

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。

后台-系统设置-扩展变量-手机广告位-内容正文底部
留言与评论(共有 0 条评论)
   
验证码: