Java b2b2c multi-user open source mall system based on script engine promotion architecture source sharing

demand analysis

Before sharing the source code, first organize and clarify the requirements of the promotion module in the b2b2c system to facilitate the understanding of the source code.

Business needs

Technical requirements

  • The promotion rules script needs to use the freemarker template engine, to which built-in variables need to be set.

  • The methods of rendering scripts and calling scripts are placed in the tool class, which is convenient to call at any time.

Architecture ideas

1. Script generation rules

1. Promotional activities that need to generate a script engine include: full discount, instant discount for a single product, half price for the second item, group purchase, limited-time purchase, group grouping, coupons and points redemption.

2. According to the different promotion rules, the timing of generating the script engine is also different, which can be roughly divided into four categories:

The first category: full discount and full discount, single product instant discount, second half price and coupons, these four are generated scripts when the event takes effect. A delayed task needs to be set, and the script will be automatically generated when the activity takes effect.

The second category: group grouping, because after the group grouping event takes effect, you can also add or modify the products participating in the group grouping activity, and the platform can close and open the group grouping activity, so it is slightly different from the first category, except when the activity takes effect. In addition to generating scripts, these operations also generate or update scripts.

The third category: group buying and limited-time buying. These two kinds of promotions are selected by the merchants on the platform to participate. The participating items need to be reviewed by the merchants, so scripts are generated when the review is passed.

The fourth category: Points redemption. Points redemption is aimed at commodities, so a script is generated or updated when the merchant adds and modifies commodity information.

3. Scripts generated by promotional activities need to be put into the cache to reduce library search operations.

4. Clear the useless script engine in the cache: Except for points redemption, other promotional activities need to use delayed tasks. When the promotional activities are invalid, the script data in the cache will be cleared. Points redemption only deletes the script data in the cache when the merchant closes the point redemption operation of the product.

2. Script generation flow chart

3. Cache data structure

1. According to the different rules of promotional activities, it is divided into three cache data structures, namely: SKU level cache, store level cache and coupon level cache.

2. Structure diagram:

  The SKU-level cache structure is the same as the store-level cache structure, as follows:

  The cache structure at the coupon level is as follows:

3. Description of cache structure

(1), SKU level cache:

  Cache key: {SKU PROMOTION}  plus SKU ID, for example: {SKU_PROMOTION}_100.

  Cache value: is a List collection whose generic type is PromotionScriptVO.

(2), store-level cache:

  Cache key: {CART PROMOTION}  plus store ID, for example: {CART_PROMOTION}_100.

  Cache value: is a List collection whose generic type is PromotionScriptVO.

(3) Coupon level cache:

  Cache key: {COUPON PROMOTION}  plus the ID of the coupon, for example: {COUPON_PROMOTION}_100.

  Cache value: is a script string of type String.

4. Distinguishing cache structure of promotion storage

(1) For the three promotional activities of full discount, instant discount for single product, and half price for the second item, if the merchant chooses to participate in all products when publishing the event, then the store-level cache structure is stored. If If some products are selected to participate, then the cache structure at the SKU level is stored.

(2) For promotional activities such as group grouping, group buying, display snapping, and point redemption, they are all stored SKU-level cache structures.

(3) For coupons, whether it is a store coupon or a platform coupon, the cache structure at the coupon level is stored.

Fourth, the script specification

1. The variable specification passed in by the calling script:

variable name Types of illustrate
$currentTime int The current time, in order to verify that the activity is valid
$ sku Object See the table below for details
$price double Total price after discounts for other promotions

$sku description:

name Types of illustrate
$price double Commodity price
$num int Number of Products
skuId int Product skuID
$ totalPrice double Commodity subtotal (unit price * quantity)

2. Description of the methods in each promotion script

Script method of full discount and full gift, coupon promotion activities

method name parameter return value type Return value example illustrate
validTime $currentTime Boolean true/false  
countPrice $price Double 100.00  
giveGift $price Object [{"type":"freeShip","value":true},{"type":"point","value":100},{"type":"gift","value":10},{"type":"coupon","value":20}] Coupon scripts don't have this method

Single product discount, second half price, group purchase, limited time purchase, group purchase activity script method

method name parameter return value type Return value example
validTime $currentTime Boolean true/false
countPrice $ sku Double 100.00

Points redemption activity script method

method name parameter return value type Return value example illustrate
validTime $currentTime Boolean true/false This method will return true directly. Points redemption does not involve the validity period. This method is included in the script to unify the content of the script.
countPrice $ sku Double 100.00  
countPoint $ sku Integer 50

Source code sharing

Since there are many types of promotional activities, only group purchase activities are used as an example to share relevant codes.

ScriptUtil

Promotion script rendering and calling tools

1 import com.enation.app.javashop.framework.logs.Logger;
  2 import com.enation.app.javashop.framework.logs.LoggerFactory;
  3 import freemarker.template.Configuration;
  4 import freemarker.template.Template;
  5 
  6 import javax.script.Invocable;
  7 import javax.script.ScriptEngine;
  8 import javax.script.ScriptEngineManager;
  9 import javax.script.ScriptException;
 10 import java.io.IOException;
 11 import java.io.StringWriter;
 12 import java.util.*;
 13 
 14 /**
 15  * 脚本生成工具类
 16  * @author duanmingyu
 17  * @version v1.0
 18  * @since v7.2.0
 19  * @date 2020-01-06
 20  */
 21 public class ScriptUtil {
 22 
 23     private static final Logger log = LoggerFactory.getLogger(ScriptUtil.class);
 24 
 25     /**
 26      * 渲染并读取脚本内容
 27      * @param name 脚本模板名称(例:test.js,test.html,test.ftl等)
 28      * @param model 渲染脚本需要的数据内容
 29      * @return
 30      */
 31     public static String renderScript(String name, Map<String, Object> model) {
 32         StringWriter stringWriter = new StringWriter();
 33 
 34         try {
 35             Configuration cfg = new Configuration(Configuration.VERSION_2_3_28);
 36 
 37             cfg.setClassLoaderForTemplateLoading(Thread.currentThread().getContextClassLoader(),"/script_tpl");
 38             cfg.setDefaultEncoding("UTF-8");
 39             cfg.setNumberFormat("#.##");
 40 
 41             Template temp = cfg.getTemplate(name);
 42 
 43             temp.process(model, stringWriter);
 44 
 45             stringWriter.flush();
 46 
 47             return stringWriter.toString();
 48 
 49         } catch (Exception e) {
 50             log.error(e.getMessage());
 51         } finally {
 52             try {
 53                 stringWriter.close();
 54             } catch (IOException ex) {
 55                 log.error(ex.getMessage());
 56             }
 57         }
 58 
 59         return null;
 60     }
 61 
 62     /**
 63      * @Description:执行script脚本
 64      * @param method script方法名
 65      * @param params 参数
 66      * @param script 脚本
 67      * @return: 返回执行结果
 68      * @Author: liuyulei
 69      * @Date: 2020/1/7
 70      */
 71     public static Object executeScript(String method,Map<String,Object> params,String script)  {
 72         if (StringUtil.isEmpty(script)){
 73             log.debug("script is " + script);
 74             return new Object();
 75         }
 76 
 77         try {
 78             ScriptEngineManager manager = new ScriptEngineManager();
 79             ScriptEngine engine = manager.getEngineByName("javascript");
 80 
 81 
 82             log.debug("脚本参数:");
 83             for (String key:params.keySet()) {
 84                 log.debug(key + "=" + params.get(key));
 85                 engine.put(key, params.get(key));
 86             }
 87 
 88             engine.eval(script);
 89             log.debug("script 脚本 :");
 90             log.debug(script);
 91 
 92             Invocable invocable = (Invocable) engine;
 93 
 94             return invocable.invokeFunction(method);
 95         } catch (ScriptException e) {
 96             log.error(e.getMessage(),e);
 97         } catch (NoSuchMethodException e) {
 98             log.error(e.getMessage(),e);
 99         }
100         return new Object();
101     }
102 }

groupbuy.ftl

Group buying activity script template

 1 <#--
 2  验证促销活动是否在有效期内
 3  @param promotionActive 活动信息对象(内置常量)
 4         .startTime 获取开始时间
 5         .endTime 活动结束时间
 6  @param $currentTime 当前时间(变量)
 7  @returns {boolean}
 8  -->
 9 function validTime(){
10     if (${promotionActive.startTime} <= $currentTime && $currentTime <= ${promotionActive.endTime}) {
11         return true;
12     }
13     return false;
14 }
15 
16 <#--
17 活动金额计算
18 @param promotionActive 活动信息对象(内置常量)
19        .price 商品促销活动价格
20 @param $sku 商品SKU信息对象(变量)
21        .$num 商品数量
22 @returns {*}
23 -->
24 function countPrice() {
25     var resultPrice = $sku.$num * ${promotionActive.price};
26     return resultPrice < 0 ? 0 : resultPrice.toString();
27 }

PromotionScriptVO

Promotion Script Data Structure Entity

1 import com.fasterxml.jackson.databind.PropertyNamingStrategy;
  2 import com.fasterxml.jackson.databind.annotation.JsonNaming;
  3 import io.swagger.annotations.ApiModelProperty;
  4 import org.apache.commons.lang.builder.EqualsBuilder;
  5 import org.apache.commons.lang.builder.HashCodeBuilder;
  6 
  7 import java.io.Serializable;
  8 
  9 /**
 10  * @description: 促销脚本VO
 11  * @author: liuyulei
 12  * @create: 2020-01-09 09:43
 13  * @version:1.0
 14  * @since:7.1.5
 15  **/
 16 @JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
 17 public class PromotionScriptVO implements Serializable {
 18     private static final long serialVersionUID = 3566902764098210013L;
 19 
 20     @ApiModelProperty(value = "促销活动id")
 21     private Integer promotionId;
 22 
 23     @ApiModelProperty(value = "促销活动名称")
 24     private String promotionName;
 25 
 26     @ApiModelProperty(value = "促销活动类型")
 27     private String promotionType;
 28 
 29     @ApiModelProperty(value = "是否可以被分组")
 30     private Boolean isGrouped;
 31 
 32     @ApiModelProperty(value = "促销脚本",hidden = true)
 33     private String promotionScript;
 34 
 35     @ApiModelProperty(value = "商品skuID")
 36     private Integer skuId;
 37 
 38 
 39     public Integer getPromotionId() {
 40         return promotionId;
 41     }
 42 
 43     public void setPromotionId(Integer promotionId) {
 44         this.promotionId = promotionId;
 45     }
 46 
 47     public String getPromotionName() {
 48         return promotionName;
 49     }
 50 
 51     public void setPromotionName(String promotionName) {
 52         this.promotionName = promotionName;
 53     }
 54 
 55     public String getPromotionType() {
 56         return promotionType;
 57     }
 58 
 59     public void setPromotionType(String promotionType) {
 60         this.promotionType = promotionType;
 61     }
 62 
 63     public Boolean getIsGrouped() {
 64         return isGrouped;
 65     }
 66 
 67     public void setIsGrouped(Boolean grouped) {
 68         isGrouped = grouped;
 69     }
 70 
 71     public String getPromotionScript() {
 72         return promotionScript;
 73     }
 74 
 75     public void setPromotionScript(String promotionScript) {
 76         this.promotionScript = promotionScript;
 77     }
 78 
 79     public Integer getSkuId() {
 80         return skuId;
 81     }
 82 
 83     public void setSkuId(Integer skuId) {
 84         this.skuId = skuId;
 85     }
 86 
 87     @Override
 88     public boolean equals(Object o) {
 89         if (this == o) {
 90             return true;
 91         }
 92 
 93         if (o == null || getClass() != o.getClass()) {
 94             return false;
 95         }
 96         PromotionScriptVO that = (PromotionScriptVO) o;
 97 
 98         return new EqualsBuilder()
 99                 .append(promotionId, that.promotionId)
100                 .append(promotionName, that.promotionName)
101                 .append(promotionType, that.promotionType)
102                 .append(isGrouped, that.isGrouped)
103                 .isEquals();
104     }
105 
106     @Override
107     public int hashCode() {
108         return new HashCodeBuilder(17, 37)
109                 .append(promotionId)
110                 .append(promotionName)
111                 .append(promotionType)
112                 .append(isGrouped)
113                 .toHashCode();
114     }
115 
116     @Override
117     public String toString() {
118         return "PromotionScriptVO{" +
119                 "promotionId=" + promotionId +
120                 ", promotionName='" + promotionName + '\'' +
121                 ", promotionType='" + promotionType + '\'' +
122                 ", isGrouped=" + isGrouped +
123                 ", promotionScript='" + promotionScript + '\'' +
124                 ", skuId=" + skuId +
125                 '}';
126     }
127 }

GroupbuyScriptManager

Group purchase promotion script business interface

1 import com.enation.app.javashop.core.promotion.tool.model.dos.PromotionGoodsDO;
 2 
 3 import java.util.List;
 4 
 5 /**
 6  * 团购促销活动脚本业务接口
 7  * @author duanmingyu
 8  * @version v1.0
 9  * @since v7.2.0
10  * 2020-02-18
11  */
12 public interface GroupbuyScriptManager {
13 
14     /**
15      * 创建参与团购促销活动商品的脚本数据信息
16      * @param promotionId 团购促销活动ID
17      * @param goodsList 参与团购促销活动的商品集合
18      */
19     void createCacheScript(Integer promotionId, List<PromotionGoodsDO> goodsList);
20 
21     /**
22      * 删除商品存放在缓存中的团购促销活动相关的脚本数据信息
23      * @param promotionId 团购促销活动ID
24      * @param goodsList 参与团购促销活动的商品集合
25      */
26     void deleteCacheScript(Integer promotionId, List<PromotionGoodsDO> goodsList);
27 }

GroupbuyScriptManagerImpl

Implementation of the script business interface for group purchase promotion activities

   1 import com.enation.app.javashop.core.base.CachePrefix;
  2 import com.enation.app.javashop.core.promotion.groupbuy.model.dos.GroupbuyActiveDO;
  3 import com.enation.app.javashop.core.promotion.groupbuy.service.GroupbuyActiveManager;
  4 import com.enation.app.javashop.core.promotion.groupbuy.service.GroupbuyScriptManager;
  5 import com.enation.app.javashop.core.promotion.tool.model.dos.PromotionGoodsDO;
  6 import com.enation.app.javashop.core.promotion.tool.model.enums.PromotionTypeEnum;
  7 import com.enation.app.javashop.core.promotion.tool.model.vo.PromotionScriptVO;
  8 import com.enation.app.javashop.framework.cache.Cache;
  9 import com.enation.app.javashop.framework.logs.Logger;
 10 import com.enation.app.javashop.framework.logs.LoggerFactory;
 11 import com.enation.app.javashop.framework.util.ScriptUtil;
 12 import org.springframework.beans.factory.annotation.Autowired;
 13 import org.springframework.stereotype.Service;
 14 
 15 import java.util.ArrayList;
 16 import java.util.HashMap;
 17 import java.util.List;
 18 import java.util.Map;
 19 
 20 /**
 21  * 团购促销活动脚本业务接口
 22  * @author duanmingyu
 23  * @version v1.0
 24  * @since v7.2.0
 25  * 2020-02-18
 26  */
 27 @Service
 28 public class GroupbuyScriptManagerImpl implements GroupbuyScriptManager {
 29 
 30     protected final Logger logger = LoggerFactory.getLogger(this.getClass());
 31 
 32     @Autowired
 33     private Cache cache;
 34 
 35     @Autowired
 36     private GroupbuyActiveManager groupbuyActiveManager;
 37 
 38     @Override
 39     public void createCacheScript(Integer promotionId, List<PromotionGoodsDO> goodsList) {
 40         //如果参与团购促销活动的商品集合不为空并且集合长度不为0
 41         if (goodsList != null && goodsList.size() != 0) {
 42             //获取团购活动详细信息
 43             GroupbuyActiveDO groupbuyActiveDO = this.groupbuyActiveManager.getModel(promotionId);
 44 
 45             //批量放入缓存的数据集合
 46             Map<String, List<PromotionScriptVO>> cacheMap = new HashMap<>();
 47 
 48             //循环参与团购活动的商品集合,将脚本放入缓存中
 49             for (PromotionGoodsDO goods : goodsList) {
 50 
 51                 //缓存key
 52                 String cacheKey = CachePrefix.SKU_PROMOTION.getPrefix() + goods.getSkuId();
 53 
 54                 //获取拼团活动脚本信息
 55                 PromotionScriptVO scriptVO = new PromotionScriptVO();
 56 
 57                 //渲染并读取团购促销活动脚本信息
 58                 String script = renderScript(groupbuyActiveDO.getStartTime().toString(), groupbuyActiveDO.getEndTime().toString(), goods.getPrice());
 59 
 60                 scriptVO.setPromotionScript(script);
 61                 scriptVO.setPromotionId(promotionId);
 62                 scriptVO.setPromotionType(PromotionTypeEnum.GROUPBUY.name());
 63                 scriptVO.setIsGrouped(false);
 64                 scriptVO.setPromotionName("团购");
 65                 scriptVO.setSkuId(goods.getSkuId());
 66 
 67                 //从缓存中读取脚本信息
 68                 List<PromotionScriptVO> scriptList = (List<PromotionScriptVO>) cache.get(cacheKey);
 69                 if (scriptList == null) {
 70                     scriptList = new ArrayList<>();
 71                 }
 72 
 73                 scriptList.add(scriptVO);
 74 
 75                 cacheMap.put(cacheKey, scriptList);
 76             }
 77 
 78             //将sku促销脚本数据批量放入缓存中
 79             cache.multiSet(cacheMap);
 80         }
 81     }
 82 
 83     @Override
 84     public void deleteCacheScript(Integer promotionId, List<PromotionGoodsDO> goodsList) {
 85         //如果参与团购促销活动的商品集合不为空并且集合长度不为0
 86         if (goodsList != null && goodsList.size() != 0) {
 87             //需要批量更新的缓存数据集合
 88             Map<String, List<PromotionScriptVO>> updateCacheMap = new HashMap<>();
 89 
 90             //需要批量删除的缓存key集合
 91             List<String> delKeyList = new ArrayList<>();
 92 
 93             for (PromotionGoodsDO goods : goodsList) {
 94                 //缓存key
 95                 String cacheKey = CachePrefix.SKU_PROMOTION.getPrefix() + goods.getSkuId();
 96 
 97                 //从缓存中读取促销脚本缓存
 98                 List<PromotionScriptVO> scriptCacheList = (List<PromotionScriptVO>) cache.get(cacheKey);
 99 
100                 if (scriptCacheList != null && scriptCacheList.size() != 0) {
101                     //循环促销脚本缓存数据集合
102                     for (PromotionScriptVO script : scriptCacheList) {
103                         //如果脚本数据的促销活动信息与当前修改的促销活动信息一致,那么就将此信息删除
104                         if (PromotionTypeEnum.GROUPBUY.name().equals(script.getPromotionType())
105                                 && script.getPromotionId().intValue() == promotionId.intValue()) {
106                             scriptCacheList.remove(script);
107                             break;
108                         }
109                     }
110 
111                     if (scriptCacheList.size() == 0) {
112                         delKeyList.add(cacheKey);
113                     } else {
114                         updateCacheMap.put(cacheKey, scriptCacheList);
115                     }
116                 }
117             }
118 
119             cache.multiDel(delKeyList);
120             cache.multiSet(updateCacheMap);
121         }
122     }
123 
124     /**
125      * 渲染并读取团购促销活动脚本信息
126      * @param startTime 活动开始时间
127      * @param endTime 活动结束时间
128      * @param price 限时抢购商品价格
129      * @return
130      */
131     private String renderScript(String startTime, String endTime, Double price) {
132         Map<String, Object> model = new HashMap<>();
133 
134         Map<String, Object> params = new HashMap<>();
135         params.put("startTime", startTime);
136         params.put("endTime", endTime);
137         params.put("price", price);
138 
139         model.put("promotionActive", params);
140 
141         String path = "groupbuy.ftl";
142         String script = ScriptUtil.renderScript(path, model);
143 
144         logger.debug("生成团购促销活动脚本:" + script);
145 
146         return script;
147     }
148 }

 The above is the idea of ​​​​the promotion activity structure based on the script engine in Javashop and some source code sharing.

Original article by javashop

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324134728&siteId=291194637