6.28 人群标签节点过滤,活动是否对xx用户可见、可参与
This commit is contained in:
parent
b09e3bae76
commit
8ff266ff41
@ -95,7 +95,7 @@ CREATE TABLE `group_buy_activity` (
|
||||
-- ----------------------------
|
||||
-- Records of group_buy_activity
|
||||
-- ----------------------------
|
||||
INSERT INTO `group_buy_activity` VALUES (1, 100123, '测试活动', '25120207', 0, 1, 1, 15, 1, '2025-06-19 10:19:40', '2025-06-19 10:19:40', '1', '1', '2025-06-19 10:19:40', '2025-06-26 15:27:48');
|
||||
INSERT INTO `group_buy_activity` VALUES (1, 100123, '测试活动', '25120207', 0, 1, 1, 15, 1, '2025-06-19 10:19:40', '2025-06-19 10:19:40', 'RQ_KJHKL98UU78H66554GFDV', '1', '2025-06-19 10:19:40', '2025-06-26 15:27:48');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for group_buy_discount
|
||||
|
@ -23,6 +23,9 @@ public class IIndexGroupBuyMarketServiceTest {
|
||||
@Resource
|
||||
private IIndexGroupBuyMarketService indexGroupBuyMarketService;
|
||||
|
||||
/**
|
||||
* 测试人群标签功能的时候,可以进入 ITagServiceTest#test_tag_job 执行人群写入
|
||||
*/
|
||||
@Test
|
||||
public void test_indexMarketTrial() throws Exception {
|
||||
MarketProductEntity marketProductEntity = new MarketProductEntity();
|
||||
@ -36,6 +39,19 @@ public class IIndexGroupBuyMarketServiceTest {
|
||||
log.info("返回结果:{}", JSON.toJSONString(trialBalanceEntity));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_indexMarketTrial_no_tag() throws Exception {
|
||||
MarketProductEntity marketProductEntity = new MarketProductEntity();
|
||||
marketProductEntity.setUserId("user01");
|
||||
marketProductEntity.setSource("s01");
|
||||
marketProductEntity.setChannel("c01");
|
||||
marketProductEntity.setGoodsId("9890001");
|
||||
|
||||
TrialBalanceEntity trialBalanceEntity = indexGroupBuyMarketService.indexMarketTrial(marketProductEntity);
|
||||
log.info("请求参数:{}", JSON.toJSONString(marketProductEntity));
|
||||
log.info("返回结果:{}", JSON.toJSONString(trialBalanceEntity));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_indexMarketTrial_error() throws Exception {
|
||||
MarketProductEntity marketProductEntity = new MarketProductEntity();
|
||||
|
@ -36,4 +36,10 @@ public class ITagServiceTest {
|
||||
log.info("gudebai 不存在,预期结果为 false,测试结果:{}", bitSet.get(redisService.getIndexFromUserId("gudebai")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_null_tag_bitmap() {
|
||||
RBitSet bitSet = redisService.getBitSet("null");
|
||||
log.info("测试结果:{}", bitSet.isExists());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,4 +14,6 @@ public interface IActivityRepository {
|
||||
|
||||
SCSkuActivityVO querySCSkuActivityBySCGoodsId(String source, String channel, String goodsId);
|
||||
|
||||
boolean isTagCrowdRange(String tagId, String userId);
|
||||
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
package edu.whut.domain.activity.model.valobj;
|
||||
|
||||
import edu.whut.types.common.Constants;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 拼团活动营销配置值对象
|
||||
@ -73,10 +76,36 @@ public class GroupBuyActivityDiscountVO {
|
||||
*/
|
||||
private String tagId;
|
||||
/**
|
||||
* 人群标签规则范围
|
||||
* 人群标签规则范围,如果有值,说明有限制
|
||||
*/
|
||||
private String tagScope;
|
||||
|
||||
/**
|
||||
* 可见限制,可以看见不等于能参加拼团
|
||||
* 只要存在这样一个值,那么首次获得的默认值就是 false,false代表有限制
|
||||
*/
|
||||
public boolean isVisible() {
|
||||
if(StringUtils.isBlank(this.tagScope)) return TagScopeEnumVO.VISIBLE.getAllow(); //等价于return true,放行
|
||||
String[] split = this.tagScope.split(Constants.SPLIT);
|
||||
if (split.length > 0 && Objects.equals(split[0], "1") && StringUtils.isNotBlank(split[0])) {
|
||||
return TagScopeEnumVO.VISIBLE.getRefuse(); //等价于return false,待后续校验
|
||||
}
|
||||
return TagScopeEnumVO.VISIBLE.getAllow();
|
||||
}
|
||||
|
||||
/**
|
||||
* 参与限制
|
||||
* 只要存在这样一个值,那么首次获得的默认值就是 false
|
||||
*/
|
||||
public boolean isEnable() {
|
||||
if(StringUtils.isBlank(this.tagScope)) return TagScopeEnumVO.VISIBLE.getAllow();
|
||||
String[] split = this.tagScope.split(Constants.SPLIT);
|
||||
if (split.length == 2 && Objects.equals(split[1], "2") && StringUtils.isNotBlank(split[1])) {
|
||||
return TagScopeEnumVO.ENABLE.getRefuse();
|
||||
}
|
||||
return TagScopeEnumVO.ENABLE.getAllow();
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
|
@ -0,0 +1,23 @@
|
||||
package edu.whut.domain.activity.model.valobj;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 活动人群标签作用域范围枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public enum TagScopeEnumVO {
|
||||
|
||||
VISIBLE(true,false,"是否可看见拼团"),
|
||||
ENABLE(true, false,"是否可参与拼团"),
|
||||
;
|
||||
|
||||
private Boolean allow;
|
||||
private Boolean refuse;
|
||||
private String desc;
|
||||
|
||||
}
|
@ -17,7 +17,7 @@ public class MJCalculateService extends AbstractDiscountCalculateService {
|
||||
|
||||
@Override
|
||||
public BigDecimal doCalculate(BigDecimal originalPrice, GroupBuyActivityDiscountVO.GroupBuyDiscount groupBuyDiscount) {
|
||||
log.info("优惠策略折扣计算:{}", groupBuyDiscount.getDiscountType().getCode());
|
||||
log.info("满减优惠策略折扣计算规则,0代表人人可参与,1代表指定标签人群参与:{}", groupBuyDiscount.getDiscountType().getCode());
|
||||
|
||||
// 获取折扣表达式 - 100,10 满100减10元
|
||||
String marketExpr = groupBuyDiscount.getMarketExpr();
|
||||
|
@ -15,7 +15,7 @@ public class NCalculateService extends AbstractDiscountCalculateService {
|
||||
|
||||
@Override
|
||||
public BigDecimal doCalculate(BigDecimal originalPrice, GroupBuyActivityDiscountVO.GroupBuyDiscount groupBuyDiscount) {
|
||||
log.info("优惠策略折扣计算:{}", groupBuyDiscount.getDiscountType().getCode());
|
||||
log.info("N元购优惠策略折扣计算规则,0代表人人可参与,1代表指定标签人群参与:{}", groupBuyDiscount.getDiscountType().getCode());
|
||||
|
||||
// 折扣表达式 - 直接为优惠后的金额
|
||||
String marketExpr = groupBuyDiscount.getMarketExpr();
|
||||
|
@ -15,12 +15,12 @@ public class ZJCalculateService extends AbstractDiscountCalculateService {
|
||||
|
||||
@Override
|
||||
public BigDecimal doCalculate(BigDecimal originalPrice, GroupBuyActivityDiscountVO.GroupBuyDiscount groupBuyDiscount) {
|
||||
log.info("优惠策略折扣计算:{}", groupBuyDiscount.getDiscountType().getCode());
|
||||
log.info("直减优惠策略折扣计算规则,0代表人人可参与,1代表指定标签人群参与:{}", groupBuyDiscount.getDiscountType().getCode());
|
||||
|
||||
// 折扣表达式 - 直减为扣减金额
|
||||
String marketExpr = groupBuyDiscount.getMarketExpr();
|
||||
|
||||
// 折扣价格
|
||||
// 折扣后的实际支付金额
|
||||
BigDecimal deductionPrice = originalPrice.subtract(new BigDecimal(marketExpr));
|
||||
|
||||
// 判断折扣后金额,最低支付1分钱
|
||||
|
@ -15,7 +15,7 @@ public class ZKCalculateService extends AbstractDiscountCalculateService {
|
||||
|
||||
@Override
|
||||
public BigDecimal doCalculate(BigDecimal originalPrice, GroupBuyActivityDiscountVO.GroupBuyDiscount groupBuyDiscount) {
|
||||
log.info("优惠策略折扣计算:{}", groupBuyDiscount.getDiscountType().getCode());
|
||||
log.info("折扣优惠策略折扣计算规则,0代表人人可参与,1代表指定标签人群参与:{}", groupBuyDiscount.getDiscountType().getCode());
|
||||
|
||||
// 折扣表达式 - 折扣百分比
|
||||
String marketExpr = groupBuyDiscount.getMarketExpr();
|
||||
|
@ -34,6 +34,10 @@ public class DefaultActivityStrategyFactory {
|
||||
private SkuVO skuVO;
|
||||
// 折扣价格
|
||||
private BigDecimal deductionPrice;
|
||||
// 活动可见性限制
|
||||
private boolean visible;
|
||||
// 活动
|
||||
private boolean enable;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,8 +7,6 @@ import edu.whut.domain.activity.model.valobj.SkuVO;
|
||||
import edu.whut.domain.activity.service.trial.AbstractGroupBuyMarketSupport;
|
||||
import edu.whut.domain.activity.service.trial.factory.DefaultActivityStrategyFactory;
|
||||
import edu.whut.types.design.framework.tree.StrategyHandler;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@ -24,8 +22,9 @@ public class EndNode extends AbstractGroupBuyMarketSupport<MarketProductEntity,
|
||||
@Override
|
||||
public TrialBalanceEntity doApply(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {
|
||||
log.info("拼团商品查询试算服务-EndNode userId:{} requestParameter:{}", requestParameter.getUserId(), JSON.toJSONString(requestParameter));
|
||||
|
||||
// 拼团活动配置
|
||||
GroupBuyActivityDiscountVO groupBuyActivityDiscountVO = dynamicContext.getGroupBuyActivityDiscountVO();
|
||||
// 商品信息
|
||||
SkuVO skuVO = dynamicContext.getSkuVO();
|
||||
|
||||
// 折扣价格
|
||||
@ -35,12 +34,12 @@ public class EndNode extends AbstractGroupBuyMarketSupport<MarketProductEntity,
|
||||
.goodsId(skuVO.getGoodsId())
|
||||
.goodsName(skuVO.getGoodsName())
|
||||
.originalPrice(skuVO.getOriginalPrice())
|
||||
.deductionPrice(new BigDecimal("0.00"))
|
||||
.deductionPrice(deductionPrice)
|
||||
.targetCount(groupBuyActivityDiscountVO.getTarget())
|
||||
.startTime(groupBuyActivityDiscountVO.getStartTime())
|
||||
.endTime(groupBuyActivityDiscountVO.getEndTime())
|
||||
.isVisible(false)
|
||||
.isEnable(false)
|
||||
.isVisible(dynamicContext.isVisible())
|
||||
.isEnable(dynamicContext.isEnable())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,8 @@ public class MarketNode extends AbstractGroupBuyMarketSupport<MarketProductEntit
|
||||
|
||||
private final Map<String, IDiscountCalculateService> discountCalculateServiceMap;
|
||||
|
||||
private final TagNode tagNode;
|
||||
|
||||
/**
|
||||
* 异步加载数据
|
||||
* @param requestParameter
|
||||
@ -57,8 +59,10 @@ public class MarketNode extends AbstractGroupBuyMarketSupport<MarketProductEntit
|
||||
FutureTask<SkuVO> skuVOFutureTask = new FutureTask<>(querySkuVOFromDBThreadTask);
|
||||
threadPoolExecutor.execute(skuVOFutureTask);
|
||||
|
||||
// 写入上下文 - 对于一些复杂场景,获取数据的操作,有时候会在下N个节点获取,这样前置查询数据,可以提高接口响应效率
|
||||
// 写入上下文- 对于一些复杂场景,获取数据的操作,有时候会在下N个节点获取,这样前置查询数据,可以提高接口响应效率
|
||||
// 写入活动配置信息
|
||||
dynamicContext.setGroupBuyActivityDiscountVO(groupBuyActivityDiscountVOFutureTask.get(timeout, TimeUnit.MINUTES));
|
||||
// 写入商品信息
|
||||
dynamicContext.setSkuVO(skuVOFutureTask.get(timeout, TimeUnit.MINUTES));
|
||||
|
||||
log.info("拼团商品查询试算服务-MarketNode userId:{} 异步线程加载数据「GroupBuyActivityDiscountVO、SkuVO」完成", requestParameter.getUserId());
|
||||
@ -75,13 +79,14 @@ public class MarketNode extends AbstractGroupBuyMarketSupport<MarketProductEntit
|
||||
public TrialBalanceEntity doApply(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {
|
||||
log.info("拼团商品查询试算服务-MarketNode userId:{} requestParameter:{}", requestParameter.getUserId(), JSON.toJSONString(requestParameter));
|
||||
|
||||
// 获取上下文数据
|
||||
// 获取上下文数据(活动配置信息)
|
||||
GroupBuyActivityDiscountVO groupBuyActivityDiscountVO = dynamicContext.getGroupBuyActivityDiscountVO();
|
||||
if (null == groupBuyActivityDiscountVO) {
|
||||
return router(requestParameter, dynamicContext);
|
||||
}
|
||||
|
||||
GroupBuyActivityDiscountVO.GroupBuyDiscount groupBuyDiscount = groupBuyActivityDiscountVO.getGroupBuyDiscount();
|
||||
//获取商品信息
|
||||
SkuVO skuVO = dynamicContext.getSkuVO();
|
||||
if (null == groupBuyDiscount || null == skuVO) {
|
||||
return router(requestParameter, dynamicContext);
|
||||
@ -96,6 +101,7 @@ public class MarketNode extends AbstractGroupBuyMarketSupport<MarketProductEntit
|
||||
|
||||
// 折扣价格
|
||||
BigDecimal deductionPrice = discountCalculateService.calculate(requestParameter.getUserId(), skuVO.getOriginalPrice(), groupBuyDiscount);
|
||||
//设置折扣价格到上下文中
|
||||
dynamicContext.setDeductionPrice(deductionPrice);
|
||||
|
||||
return router(requestParameter, dynamicContext);
|
||||
@ -114,7 +120,7 @@ public class MarketNode extends AbstractGroupBuyMarketSupport<MarketProductEntit
|
||||
if (null == dynamicContext.getGroupBuyActivityDiscountVO() || null == dynamicContext.getSkuVO() || null == dynamicContext.getDeductionPrice()) {
|
||||
return errorNode;
|
||||
}
|
||||
return endNode;
|
||||
return tagNode;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
package edu.whut.domain.activity.service.trial.node;
|
||||
import edu.whut.domain.activity.model.entity.MarketProductEntity;
|
||||
import edu.whut.domain.activity.model.entity.TrialBalanceEntity;
|
||||
import edu.whut.domain.activity.model.valobj.GroupBuyActivityDiscountVO;
|
||||
import edu.whut.domain.activity.service.trial.AbstractGroupBuyMarketSupport;
|
||||
import edu.whut.domain.activity.service.trial.factory.DefaultActivityStrategyFactory;
|
||||
import edu.whut.types.design.framework.tree.StrategyHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 人群标签判断
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class TagNode extends AbstractGroupBuyMarketSupport<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> {
|
||||
|
||||
@Resource
|
||||
private EndNode endNode;
|
||||
|
||||
@Override
|
||||
protected TrialBalanceEntity doApply(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {
|
||||
// 获取拼团活动配置
|
||||
GroupBuyActivityDiscountVO groupBuyActivityDiscountVO = dynamicContext.getGroupBuyActivityDiscountVO();
|
||||
|
||||
String tagId = groupBuyActivityDiscountVO.getTagId();
|
||||
boolean visible = groupBuyActivityDiscountVO.isVisible();
|
||||
boolean enable = groupBuyActivityDiscountVO.isEnable();
|
||||
|
||||
// 人群标签配置为空,说明该活动不限定人群参与
|
||||
if (StringUtils.isBlank(tagId)) {
|
||||
dynamicContext.setVisible(true);
|
||||
dynamicContext.setEnable(true);
|
||||
return router(requestParameter, dynamicContext);
|
||||
}
|
||||
|
||||
// 是否在人群范围内;visible、enable 如果值为 ture 则表示没有配置拼团限制,那么就直接保证为 true 即可
|
||||
boolean isWithin = repository.isTagCrowdRange(tagId, requestParameter.getUserId());
|
||||
dynamicContext.setVisible(visible || isWithin);
|
||||
dynamicContext.setEnable(enable || isWithin);
|
||||
return router(requestParameter, dynamicContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StrategyHandler<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> get(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {
|
||||
return endNode;
|
||||
}
|
||||
|
||||
}
|
@ -13,7 +13,10 @@ import edu.whut.infrastructure.dao.po.GroupBuyActivity;
|
||||
import edu.whut.infrastructure.dao.po.GroupBuyDiscount;
|
||||
import edu.whut.infrastructure.dao.po.SCSkuActivity;
|
||||
import edu.whut.infrastructure.dao.po.Sku;
|
||||
import edu.whut.infrastructure.redis.IRedisService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RBitSet;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@ -23,6 +26,7 @@ import javax.annotation.Resource;
|
||||
*/
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class ActivityRepository implements IActivityRepository {
|
||||
|
||||
private final IGroupBuyActivityDao groupBuyActivityDao;
|
||||
@ -33,6 +37,8 @@ public class ActivityRepository implements IActivityRepository {
|
||||
|
||||
private final ISCSkuActivityDao skuActivityDao;
|
||||
|
||||
private final IRedisService redisService;
|
||||
|
||||
@Override
|
||||
public GroupBuyActivityDiscountVO queryGroupBuyActivityDiscountVO(Long activityId) {
|
||||
GroupBuyActivity groupBuyActivityRes = groupBuyActivityDao.queryValidGroupBuyActivityId(activityId);
|
||||
@ -97,4 +103,15 @@ public class ActivityRepository implements IActivityRepository {
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTagCrowdRange(String tagId, String userId) {
|
||||
//根据标签id获取对应位图
|
||||
RBitSet bitSet = redisService.getBitSet(tagId);
|
||||
if (!bitSet.isExists()) {
|
||||
return true;
|
||||
}
|
||||
// 判断用户是否存在人群中
|
||||
return bitSet.get(redisService.getIndexFromUserId(userId));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import edu.whut.infrastructure.dao.po.CrowdTagsDetail;
|
||||
import edu.whut.infrastructure.dao.po.CrowdTagsJob;
|
||||
import edu.whut.infrastructure.redis.IRedisService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RBitSet;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Repository;
|
||||
@ -21,6 +22,7 @@ import javax.annotation.Resource;
|
||||
*/
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class TagRepository implements ITagRepository {
|
||||
|
||||
private final ICrowdTagsDao crowdTagsDao;
|
||||
@ -73,11 +75,11 @@ public class TagRepository implements ITagRepository {
|
||||
|
||||
try {
|
||||
crowdTagsDetailDao.addCrowdTagsUserId(crowdTagsDetailReq);
|
||||
|
||||
// 获取BitSet
|
||||
RBitSet bitSet = redisService.getBitSet(tagId);
|
||||
bitSet.set(redisService.getIndexFromUserId(userId), true);
|
||||
bitSet.set(redisService.getIndexFromUserId(userId));
|
||||
} catch (DuplicateKeyException ignore) {
|
||||
log.info("用户id{}已在人群标签{}中",userId,tagId);
|
||||
// 忽略唯一索引冲突
|
||||
}
|
||||
}
|
||||
|
@ -283,6 +283,11 @@ public interface IRedisService {
|
||||
//位图
|
||||
RBitSet getBitSet(String key);
|
||||
|
||||
/**
|
||||
* 将userId 映射到一个哈希值,指定需存入位图的位置
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
default int getIndexFromUserId(String userId) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
|
Loading…
x
Reference in New Issue
Block a user