拼团促销活动:索引设计与代码设计解析

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

一、索引设计

  参与拼团活动的商品会创建单独的索引结构

  拼团商品索引Mapping信息如下:

  也就是说,拼团商品在生成索引的时候,只会将如上的商品信息放入到ES中。

二、代码设计

1、接口调用流程图

  商家发布促销活动

  添加参与促销活动的商品

  平台管理端对拼团活动的操作

2、相关代码展示

商家发布促销活动

  @RestController

  @RequestMapping("/seller/promotion/pintuan")

  @Api(description = "拼团")

  @Validated

  public class PintuanSellerController {

  @ApiOperation(value = "添加活动")

  @PostMapping

  public Pintuan add(@Valid Pintuan pintuan) {

  return this.pintuanManager.add(pintuan);

  }

  }

  @Service

  public class PintuanManagerImpl implements PintuanManager {

  @Override

  @Transactional(value = "tradeTransactionManager",

  propagation = Propagation.REQUIRED, rollbackFor = Exception.class)

  public Pintuan add(Pintuan pintuan) {

  //检测开始时间和结束时间

  PromotionValid.paramValid(pintuan.getStartTime(), pintuan.getEndTime(),

  1, null);

  this.verifyParam(pintuan.getStartTime(), pintuan.getEndTime());

  pintuan.setStatus(PromotionStatusEnum.WAIT.name());

  pintuan.setSellerName(UserContext.getSeller().getSellerName());

  pintuan.setCreateTime(DateUtil.getDateline());

  pintuan.setSellerId(UserContext.getSeller().getSellerId());

  //可操作状态为nothing,代表活动不可以执行任何操作

  pintuan.setOptionStatus(PintuanOptionEnum.NOTHING.name());

  this.pintuanMapper.insert(pintuan);

  //创建活动 启用延时任务

  PintuanChangeMsg pintuanChangeMsg = new PintuanChangeMsg();

  pintuanChangeMsg.setPintuanId(pintuan.getPromotionId());

  pintuanChangeMsg.setOptionType(1);

  timeTrigger.add(TimeExecute.PINTUAN_EXECUTER, pintuanChangeMsg,

  pintuan.getStartTime(), TRIGGER_PREFIX +

  pintuan.getPromotionId());

  return pintuan;

  }

  }

商家选择商品参与团购活动

  @RestController

  @RequestMapping("/seller/promotion/pintuan")

  @Api(description = "拼团")

  @Validated

  public class PintuanSellerController {

  @ApiOperation(value = "修改活动参与的商品", response = PintuanGoodsDO.class)

  @PostMapping("/goods/{id}")

  @ApiImplicitParams({

  @ApiImplicitParam(name = "id", value = "活动ID",

  required = true, dataType = "int", paramType = "path")

  })

  public void savePromotionGoods(@RequestBody @Valid List

  pintuanGoodsDOS, @ApiIgnore @PathVariable Long id) {

  this.pintuanGoodsManager.save(id, pintuanGoodsDOS);

  }

  }

  @Service

  public class PintuanGoodsManagerImpl implements PintuanGoodsManager {

  @Override

  @Transactional(value = "tradeTransactionManager",

  propagation = Propagation.REQUIRED, rollbackFor = Exception.class)

  public void save(Long pintuanId, List pintuanGoodsList) {

  //获取拼团促销活动信息详情

  Pintuan pintuan = pintuanManager.getModel(pintuanId);

  //获取本次操作之前参与拼团活动的商品信息集合

  List oldGoodsList = this.getPintuanGoodsList(pintuanId);

  Long[] skuIds = new Long[pintuanGoodsList.size()];

  for (int i = 0; i < pintuanGoodsList.size(); i++) {

  skuIds[i] = pintuanGoodsList.get(i).getSkuId();

  }

  //拼团促销活动

  List promotionAbnormalGoods

  = this.checkPromotion(skuIds, pintuan.getStartTime(), pintuan.getEndTime(),

  pintuan.getPromotionId());

  StringBuffer stringBuffer = new StringBuffer();

  if (promotionAbnormalGoods.size() > 0) {

  promotionAbnormalGoods.forEach(pGoods -> {

  stringBuffer.append("商品【" + pGoods.getGoodsName()

  + "】参与【" + PromotionTypeEnum.valueOf(

  pGoods.getPromotionType()).getPromotionName()

  + "】活动,");

  stringBuffer.append("时间冲突【" + DateUtil.toString(

  pGoods.getStartTime(), "yyyy-MM-dd HH:mm:ss")

  + "~" + DateUtil.toString(pGoods.getEndTime(),

  "yyyy-MM-dd HH:mm:ss")

  + "】;");

  });

  throw new ServiceException(PintuanErrorCode.E5015.code(),

  stringBuffer.toString());

  }

  this.pintuanGoodsMapper.delete(new QueryWrapper()

  .eq("pintuan_id", pintuan.getPromotionId()));

  pintuanGoodsList.forEach(pintuanGoodsDO -> {

  pintuanGoodsDO.setPintuanId(pintuan.getPromotionId());

  GoodsSkuVO skuVo = goodsClient.getSkuFromCache(pintuanGoodsDO.getSkuId());

  //验证拼团价格和原始价格

  if (pintuanGoodsDO.getSalesPrice() > skuVo.getPrice()) {

  throw new ServiceException(PintuanErrorCode.E5015.code(),

  skuVo.getGoodsName()

  + ",此商品的拼团价格不能大于商品销售价格");

  }

  pintuanGoodsDO.setSellerId(skuVo.getSellerId());

  pintuanGoodsDO.setSellerName(skuVo.getSellerName());

  pintuanGoodsDO.setThumbnail(skuVo.getThumbnail());

  pintuanGoodsDO.setSpecs(StringUtil.notEmpty(skuVo.getSpecs()) ?

  skuVo.getSpecs() :

  JsonUtil.objectToJson(skuVo.getSpecList()));

  pintuanGoodsDO.setOriginPrice(skuVo.getPrice());

  pintuanGoodsDO.setGoodsId(skuVo.getGoodsId());

  pintuanGoodsDO.setGoodsName(skuVo.getGoodsName());

  pintuanGoodsDO.setLockedQuantity(0);

  pintuanGoodsDO.setSoldQuantity(0);

  pintuanGoodsDO.setSn(skuVo.getSn());

  add(pintuanGoodsDO);

  });

  //如果拼团活动是进行中的状态,则同步商品的索引和脚本信息

  if (pintuan.getStatus().equals(PromotionStatusEnum.UNDERWAY.name())) {

  //同步es

  pinTuanSearchManager.syncIndexByPinTuanId(pintuanId);

  //先将本次操作之前存放在缓存中参与拼团活动的商品脚本信息删除

  this.pintuanScriptManager.deleteCacheScript(pintuanId, oldGoodsList);

  //然后为本次操作添加的活动商品创建脚本信息

  this.pintuanScriptManager.createCacheScript(pintuanId, pintuanGoodsList);

  }

  }

- 平台管理端停止和开启拼团活动

  ```java

  @RestController

  @RequestMapping("/admin/promotion/pintuan")

  @Api(description = "拼团API")

  @Validated

  public class PintuanManagerController {

  @PutMapping(value = "/{id}/close")

  @ApiOperation(value = "关闭拼团")

  @ApiImplicitParams({

  @ApiImplicitParam(name = "id", value = "要关闭的拼团入库主键", required = true,

  dataType = "int", paramType = "path")

  })

  public void close(@PathVariable Long id) {

  this.pintuanManager.manualClosePromotion(id);

  }

  @PutMapping(value = "/{id}/open")

  @ApiOperation(value = "开启拼团")

  @ApiImplicitParams({

  @ApiImplicitParam(name = "id", value = "要关闭的拼团入库主键", required = true,

  dataType = "int", paramType = "path")

  })

  public void open(@PathVariable Long id) {

  this.pintuanManager.manualOpenPromotion(id);

  }

  }

  @Service

  public class PintuanManagerImpl implements PintuanManager {

  /**

  * 手动停止一个活动

  *

  * @param promotionId

  */

  @Override

  public void manualClosePromotion(Long promotionId) {

  if (check(promotionId, 0)) {

  this.closePromotion(promotionId);

  } else {

  throw new ServiceException(PintuanErrorCode.E5012.code(),

  PintuanErrorCode.E5012.describe());

  }

  }

  /**

  * 手动开始一个活动

  *

  * @param promotionId

  */

  @Override

  public void manualOpenPromotion(Long promotionId) {

  if (check(promotionId, 1)) {

  this.openPromotion(promotionId);

  } else {

  throw new ServiceException(PintuanErrorCode.E5012.code(),

  PintuanErrorCode.E5012.describe());

  }

  }

  /**

  * 开始一个活动

  *

  * @param promotionId

  */

  @Override

  @Transactional(value = "tradeTransactionManager", propagation =

  Propagation.REQUIRED, rollbackFor = Exception.class)

  public void openPromotion(Long promotionId) {

  //获取拼团活动详情

  Pintuan pintuan = this.getModel(promotionId);

  //获取参与拼团活动的商品信息集合

  List goodsList =

  this.pintuanGoodsManager.getPintuanGoodsList(promotionId);

  UpdateWrapper updateWrapper = new UpdateWrapper<>()

  .eq("promotion_id", promotionId);

  //如果还在活动时间内

  //修改状态为进行中,活动可操作状态变成可以关闭

  if (pintuan.getEndTime() > DateUtil.getDateline()) {

  updateWrapper.set("status", PromotionStatusEnum.UNDERWAY.name());

  updateWrapper.set("option_status", PintuanOptionEnum.CAN_CLOSE.name());

  //生成拼团商品索引

  pintuanGoodsManager.addIndex(promotionId);

  //生成拼团商品脚本信息

  pintuanScriptManager.createCacheScript(promotionId, goodsList);

  } else {

  //活动时间范围外,修改状态为已结束,活动可操作状态变成nothing

  updateWrapper.set("status", PromotionStatusEnum.END.name());

  updateWrapper.set("option_status", PintuanOptionEnum.NOTHING.name());

  //删除拼团商品脚本信息

  pintuanScriptManager.deleteCacheScript(promotionId, goodsList);

  }

  this.pintuanMapper.update(null, updateWrapper);

  }

  /**

  * 停止一个活动

  *

  * @param promotionId

  */

  @Override

  @Transactional(value = "tradeTransactionManager", propagation =

  Propagation.REQUIRED, rollbackFor = Exception.class)

  public void closePromotion(Long promotionId) {

  //获取拼团活动详情

  Pintuan pintuan = this.getModel(promotionId);

  //获取参与拼团活动的商品信息集合

  List goodsList =

  this.pintuanGoodsManager.getPintuanGoodsList(promotionId);

  UpdateWrapper updateWrapper = new UpdateWrapper<>()

  .set("status", PromotionStatusEnum.END.name())

  .eq("promotion_id", promotionId);

  //如果结束时间大于当前时间

  // 可以操作为开启状态,活动状态为已结束

  if (pintuan.getEndTime() > DateUtil.getDateline()) {

  //表示可以再次开启,则不处理未成团订单,因为可以开启

  updateWrapper.set("option_status", PintuanOptionEnum.CAN_OPEN.name());

  } else {

  updateWrapper.set("option_status", PintuanOptionEnum.NOTHING.name());

  //查询所有该活动下的未成团订单(未付款,已付款未成团)

  List orderList =

  this.pintuanOrderMapper.selectList(new QueryWrapper()

  .eq("pintuan_id", promotionId)

  .and(e -> {

  e.eq("order_status", PintuanOrderStatus.new_order.name())

  .or().eq("order_status",

  PintuanOrderStatus.wait.name());

  }));

  for (PintuanOrder order : orderList) {

  pintuanOrderManager.handle(order.getOrderId());

  }

  }

  this.pintuanMapper.update(null, updateWrapper);

  //删除拼团商品索引信息

  pintuanGoodsManager.delIndex(promotionId);

  //删除拼团商品脚本信息

  pintuanScriptManager.deleteCacheScript(promotionId, goodsList);

  }

  }

  创建和删除拼团活动商品索引信息

  @Service

  public class PintuanGoodsManagerImpl implements PintuanGoodsManager {

  /**

  * 关闭一个活动的促销商品索引

  *

  * @param promotionId

  */

  @Override

  public void delIndex(Long promotionId) {

  pinTuanSearchManager.deleteByPintuanId(promotionId);

  }

  /**

  * 开启一个活动的促销商品索引

  *

  * @param promotionId

  */

  @Override

  public boolean addIndex(Long promotionId) {

  List pinTuanGoodsVOS = this.all(promotionId);

  boolean hasError = false;

  for (PinTuanGoodsVO pintuanGoods : pinTuanGoodsVOS) {

  boolean result = pinTuanSearchManager.addIndex(pintuanGoods);

  hasError = result && hasError;

  }

  return hasError;

  }

  }

  @Service

  public class PinTuanSearchManagerImpl implements PinTuanSearchManager {

  @Override

  public boolean addIndex(PinTuanGoodsVO pintuanGoods) {

  String goodsName = pintuanGoods.getGoodsName() + " " +

  SkuNameUtil.createSkuName(pintuanGoods.getSpecs());

  try {

  CacheGoods cacheGoods = goodsClient.getFromCache(pintuanGoods.getGoodsId());

  PtGoodsDoc ptGoodsDoc = new PtGoodsDoc();

  ptGoodsDoc.setCategoryId(cacheGoods.getCategoryId());

  ptGoodsDoc.setThumbnail(pintuanGoods.getThumbnail());

  ptGoodsDoc.setSalesPrice(pintuanGoods.getSalesPrice());

  ptGoodsDoc.setOriginPrice(pintuanGoods.getPrice());

  ptGoodsDoc.setGoodsName(goodsName);

  ptGoodsDoc.setBuyCount(pintuanGoods.getSoldQuantity());

  ptGoodsDoc.setGoodsId(pintuanGoods.getGoodsId());

  ptGoodsDoc.setSkuId(pintuanGoods.getSkuId());

  ptGoodsDoc.setPinTuanId(pintuanGoods.getPintuanId());

  this.addIndex(ptGoodsDoc);

  return true;

  } catch (Exception e) {

  logger.error("为拼团商品[" + goodsName + "]生成索引报错", e);

  debugger.log("为拼团商品[" + goodsName + "]生成索引报错",

  StringUtil.getStackTrace(e));

  return false;

  }

  }

  @Override

  public void deleteByPintuanId(Long pinTuanId) {

  DeleteQuery dq = dq();

  dq.setQuery(QueryBuilders.termQuery("pinTuanId", pinTuanId));

  elasticsearchTemplate.delete(dq);

  }

  @Override

  public void addIndex(PtGoodsDoc goodsDoc) {

  //对cat path特殊处理

  CategoryDO categoryDO = goodsClient.getCategory(goodsDoc.getCategoryId());

  goodsDoc.setCategoryPath(HexUtils.encode(categoryDO.getCategoryPath()));

  IndexQuery indexQuery = new IndexQuery();

  String indexName = esConfig.getIndexName() + "_"

  + EsSettings.PINTUAN_INDEX_NAME;

  indexQuery.setIndexName(indexName);

  indexQuery.setType("pintuan_goods");

  indexQuery.setId(goodsDoc.getSkuId().toString());

  indexQuery.setObject(goodsDoc);

  elasticsearchTemplate.index(indexQuery);

  logger.debug("将拼团商品将拼团商品ID[" + goodsDoc.getGoodsId() + "] "

  + goodsDoc + " 写入索引");

  }

  }

创建和删除拼团活动商品脚本引擎信息

  @Service

  public class PintuanScriptManagerImpl implements PintuanScriptManager {

  @Override

  public void createCacheScript(Long promotionId, List goodsList) {

  //如果参与拼团促销活动的商品集合不为空并且集合长度不为0

  if (goodsList != null && goodsList.size() != 0) {

  //获取拼团活动详情

  Pintuan pintuan = this.pintuanManager.getModel(promotionId);

  //批量放入缓存的脚本数据集合

  Map> cacheMap = new HashMap<>();

  //循环参与拼团促销活动的商品集合,创建商品的拼团促销活动脚本数据结构

  for (PintuanGoodsDO goods : goodsList) {

  //缓存key

  String cacheKey = CachePrefix.SKU_PROMOTION.getPrefix()

  + goods.getSkuId();

  //获取拼团活动脚本信息

  PromotionScriptVO scriptVO = new PromotionScriptVO();

  //渲染并读取满减满赠促销活动脚本信息

  String script = renderScript(pintuan.getStartTime().toString(),

  pintuan.getEndTime().toString(),

  goods.getSalesPrice());

  scriptVO.setPromotionScript(script);

  scriptVO.setPromotionId(promotionId);

  scriptVO.setPromotionType(PromotionTypeEnum.PINTUAN.name());

  scriptVO.setIsGrouped(false);

  scriptVO.setPromotionName("拼团活动");

  scriptVO.setSkuId(goods.getSkuId());

  //从缓存中读取脚本信息

  List scriptList = (List)

  cache.get(cacheKey);

  if (scriptList == null) {

  scriptList = new ArrayList<>();

  }

  scriptList.add(scriptVO);

  cacheMap.put(cacheKey, scriptList);

  }

  //将参与拼团活动商品的促销脚本数据批量放入缓存中

  cache.multiSet(cacheMap);

  }

  }

  @Override

  public void deleteCacheScript(Long promotionId, List goodsList) {

  //如果参与拼团促销活动的商品集合不为空并且集合长度不为0

  if (goodsList != null && goodsList.size() != 0) {

  //需要批量更新的缓存数据集合

  Map> updateCacheMap = new HashMap<>();

  //需要批量删除的缓存key集合

  List delKeyList = new ArrayList<>();

  for (PintuanGoodsDO goods : goodsList) {

  //缓存key

  String cacheKey = CachePrefix.SKU_PROMOTION.getPrefix() +

  goods.getSkuId();

  //从缓存中读取促销脚本缓存

  List scriptCacheList = (List)

  cache.get(cacheKey);

  if (scriptCacheList != null && scriptCacheList.size() != 0) {

  //循环促销脚本缓存数据集合

  for (PromotionScriptVO script : scriptCacheList) {

  //如果脚本数据的促销活动信息与当前修改的促销活动信息一致,那么就将此信息删除

  if (PromotionTypeEnum.PINTUAN.name()

  .equals(script.getPromotionType())

  && script.getPromotionId().intValue() ==

  promotionId.intValue()) {

  scriptCacheList.remove(script);

  break;

  }

  }

  if (scriptCacheList.size() == 0) {

  delKeyList.add(cacheKey);

  } else {

  updateCacheMap.put(cacheKey, scriptCacheList);

  }

  }

  }

  cache.multiDel(delKeyList);

  cache.multiSet(updateCacheMap);

  }

  }

  /**

  * 渲染并读取拼团促销活动脚本信息

  * @param startTime 活动开始时间

  * @param endTime 活动结束时间

  * @param price 拼团商品价格

  * @return

  */

  private String renderScript(String startTime, String endTime, Double price) {

  Map model = new HashMap<>();

  Map params = new HashMap<>();

  params.put("startTime", startTime);

  params.put("endTime", endTime);

  params.put("price", price);

  model.put("promotionActive", params);

  String path = "single_promotion.ftl";

  String script = ScriptUtil.renderScript(path, model);

  logger.debug("生成拼团促销活动脚本:" + script);

  return script;

  }

  }

拼团促销活动脚本引擎模板文件—single_promotion.ftl

  <#--

  验证促销活动是否在有效期内

  @param promotionActive 活动信息对象(内置常量)

  .startTime 获取开始时间

  .endTime 活动结束时间

  @param $currentTime 当前时间(变量)

  @returns {boolean}

  -->

  function validTime(){

  if (${promotionActive.startTime} <= $currentTime && $currentTime <= ${promotionActive.endTime}) {

  return true;

  }

  return false;

  }

  <#--活动金额计算@param promotionActive 活动信息对象(内置常量)

  .price 商品促销活动价格@param $sku 商品SKU信息对象(变量)

  .$num 商品数量@returns {*}-->

  function countPrice() {

  var resultPrice = $sku.$num * ${promotionActive.price};

  return resultPrice < 0 ? 0 : resultPrice.toString();

  }

  根据以上内容可以了解到拼团促销活动的索引设计与代码设计,想了解更多详情,可以持续关注易族智汇javashop

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

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

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