云计算百科
云计算领域专业知识百科平台

大营销平台 —— 细节优化和前端对接

一、前言

至此,活动领域就基本上完成了,后续还有一个用户返利和结算,这个在下一节实现,这一节先对前面写的活动领域和策略领域进行优化,调整一些细节问题,并且补充一部分内容。

最后对接前端,前后端联调。

二、细节优化

1.Redis编码问题

前面我们在Redis中存储的值都是序列化后的乱码,我们还是希望它可以用JSON格式存储,这样既方便我们调试,也便于运维。

在Redis配置类中解开解码器的注释,这样就可以变成JSON格式了。注意,必须是用我这个解码器,那个 RedisCodec() 有问题,不会自动把数组中的对象格式化,甚至连数组中的map对象都不行,我用这个出了很多问题,一直报NPE,AI都没找出来这个问题,最后是我亲自打了无数个断点才找出来的。

config.setCodec(new JsonJacksonCodec());

@Bean("redissonClient")
public RedissonClient redissonClient(ConfigurableApplicationContext applicationContext, RedisClientConfigProperties properties) {
Config config = new Config();
// 根据需要可以设定编解码器;https://github.com/redisson/redisson/wiki/4.-%E6%95%B0%E6%8D%AE%E5%BA%8F%E5%88%97%E5%8C%96
config.setCodec(new new JsonJacksonCodec());

config.useSingleServer()
.setAddress("redis://" + properties.getHost() + ":" + properties.getPort())
.setPassword(properties.getPassword())
.setConnectionPoolSize(properties.getPoolSize())
.setConnectionMinimumIdleSize(properties.getMinIdleSize())
.setIdleConnectionTimeout(properties.getIdleTimeout())
.setConnectTimeout(properties.getConnectTimeout())
.setRetryAttempts(properties.getRetryAttempts())
.setRetryInterval(properties.getRetryInterval())
.setPingConnectionInterval(properties.getPingInterval())
.setKeepAlive(properties.isKeepAlive())
.setDatabase(properties.getDatabase())
;

return Redisson.create(config);
}

后续测试会发现可能会出现这个问题:

java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to com.bigmarket.domain.strategy.model.entity.StrategyAwardEntity

这个问题是由于fastjson默认不会将存入Redis的数组中的元素按照@type标记的类来解析,而是会直接解析为一个普通的JSONObject,这就导致解析出来的对象无法转换为我们需要的实体类。

解决方法是修改配置类下面的解码器,增加参数Feature.SupportAutoType,这样就强制让fastjson去自动解析数组中的元素了。

private final Decoder<Object> decoder = (buf, state) ->
JSON.parseObject(new ByteBufInputStream(buf), Object.class, Feature.SupportAutoType);

2.活动结束后清理缓存

目前我们的缓存全部是持久化的,没有设置过期时间,这就导致即使活动都已经结束了,但是这个过期活动的缓存还存在Redis里面占地方,所以我们应当将策略部分的键值对(sku库存和奖品库存)设置过期时间至活动结束,基于安全性考虑,我们会多延长一天,也就是在活动结束后一天清理。

既然是在活动结束后一天清理,那必然要先知道活动多久结束,前面我们在写策略时没有考虑到这一点,因此现在要将endDateTime作为参数透传进去。

于是要修改抽奖因子实体类:

RaffleAwardEntity raffleAwardEntity = raffleStrategy.performRaffle(raffleFactorEntity);

/**
* @author 印东升
* @description 抽奖因子实体
* @create 2026-04-10 11:12
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RaffleFactorEntity {
/**
* 用户id
*/
private String userId;
/**
* 策略id
*/
private Long strategyId;
/**
* 结束时间
*/
private Date endDateTime;
}

自然的,整个调用链从上层的活动Controller到模板类中的performRaffle,再到规则树的扣减库存节点,最后在仓储中设置过期时间。这整个调用链很复杂,基本上要从活动到抽奖全部透传一遍。

@Override
public Boolean subtractionAwardStock(String cacheKey, Date endDateTime) {
long surplus = redisService.decr(cacheKey);
if (surplus < 0) {
redisService.setValue(cacheKey, 0);
return false;
}
String lockKey = cacheKey + Constants.UNDERLINE + surplus;

Boolean lock = false;
if (null != endDateTime) {
long expireMills = endDateTime.getTime() – System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1);
lock = redisService.setNx(lockKey, expireMills, TimeUnit.MILLISECONDS);
} else {
lock = redisService.setNx(lockKey);
}

if (!lock) {
log.info("策略奖品库存加锁失败:{}", lockKey);
}
return lock;

}

而sku的库存缓存我们前面已经写了,上面这个和sku的基本上类似。

这里其实还可以优化,所有的对应活动的缓存其实都可以清理,但是除去sku库存锁和award库存锁,其余的剩下的并不多,只有奖品列表、规则树、活动信息等缓存,最多也就十来个,但是这两个库存锁基本上和库存量相当,所以这里只清理库存锁缓存也是没问题的。

3.次数锁显示优化

前面我们配置了奖品的次数锁规则,这里我们就需要把这个锁显示给用户了,因此我们需要告诉用户还有多少次这个奖品就能解锁,并且要让用户知道这个奖品解锁没有。

从上向下改,首先响应给前端的DTO中就要新增三个次数锁相关的字段:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RaffleAwardListResponseDTO {
//奖品Id
private Integer awardId;
//奖品标题
private String awardTitle;
//奖品副标题【抽奖1次后解锁】
private String awardSubtitle;
//排序编号
private Integer sort;
//奖品次数规则 – 抽奖N次后解锁,未配置则为空
private Integer awardRuleLockCount;
//奖品是否解锁
private Boolean isAwardUnlock;
//等待解锁次数-规则的抽奖N次解锁 – 用户已经抽奖次数
private Integer waitUnlockCount;
}

由此我们也需要修改奖品列表的Controller接口:

/**
* 查询奖品列表
* <a href="http://localhost:8091/api/v1/raffle/query_raffle_award_list">/api/v1/raffle/query_raffle_award_list</a>
* 请求参数 raw json
*
* @param request {"strategyId":1000001}
* @return 奖品列表
*/
@RequestMapping(value = "query_raffle_award_list", method = RequestMethod.POST)
@Override
public Response<List<RaffleAwardListResponseDTO>> queryRaffleAwardList(@RequestBody RaffleAwardListRequestDTO request) {
try {
log.info("查询抽奖奖品列表配置开始 userId:{} activityId:{}", request.getUserId(), request.getActivityId());
//1.参数校验
if (StrUtil.isBlank(request.getUserId()) || null == request.getActivityId()) {
return Response.<List<RaffleAwardListResponseDTO>>builder()
.code(ResponseCode.ILLEGAL_PARAMETER.getCode())
.info(ResponseCode.ILLEGAL_PARAMETER.getInfo())
.build();
}

//2.查询奖品配置
List<StrategyAwardEntity> strategyAwardEntities = raffleAward.queryRaffleStrategyAwardListByActivityId(request.getActivityId());
//3.获取规则配置
String[] treeIds = strategyAwardEntities.stream()
.map(StrategyAwardEntity::getRuleModels)
.filter(ruleModel -> ruleModel != null && !ruleModel.isEmpty())
.toArray(String[]::new);

//4.查询规则配置,获取奖品的解锁规则,抽奖N次后解锁
Map<String, Integer> ruleLockCountMap = raffleRule.queryAwardRuleLockCount(treeIds);
//5.查询抽奖次数 – 用户已经参与抽奖的次数
Integer dayPartakeCount = raffleActivityAccountQuotaService.queryRaffleActivityAccountDayPartakeCount(request.getActivityId(), request.getUserId());

//6.遍历填充数据
List<RaffleAwardListResponseDTO> raffleAwardListResponseDTOS = new ArrayList<>(strategyAwardEntities.size());
for (StrategyAwardEntity strategyAward : strategyAwardEntities) {
Integer awardRuleLockCount = ruleLockCountMap.get(strategyAward.getRuleModels());
raffleAwardListResponseDTOS.add(RaffleAwardListResponseDTO.builder()
.awardId(strategyAward.getAwardId())
.awardTitle(strategyAward.getAwardTitle())
.awardSubtitle(strategyAward.getAwardSubtitle())
.sort(strategyAward.getSort())
.awardRuleLockCount(awardRuleLockCount)
.isAwardUnlock(null == awardRuleLockCount || dayPartakeCount > awardRuleLockCount)
.waitUnlockCount(null == awardRuleLockCount || awardRuleLockCount <= dayPartakeCount ? 0 : awardRuleLockCount – dayPartakeCount)
.build());
}
Response<List<RaffleAwardListResponseDTO>> response = Response.<List<RaffleAwardListResponseDTO>>builder()
.code(ResponseCode.SUCCESS.getCode())
.info(ResponseCode.SUCCESS.getInfo())
.data(raffleAwardListResponseDTOS)
.build();
log.info("查询抽奖奖品列表配置完成 userId:{} activityId:{}", request.getUserId(), request.getActivityId());
return response;

} catch (Exception e) {
log.error("查询抽奖奖品列表配置失败 userId:{} activityId:{}", request.getUserId(), request.getActivityId(),e);
return Response.<List<RaffleAwardListResponseDTO>>builder()
.code(ResponseCode.UN_ERROR.getCode())
.info(ResponseCode.UN_ERROR.getInfo())
.build();
}
}

其中主要新增的是下面这段逻辑,本质是通过查询规则配置,筛选出所有有次数锁规则的奖品,然后根据配置的锁次数和当日用户的抽奖次数来判断是否解锁,或者还有几次解锁。注意这里我们的设定是这几个奖品的次数锁每日刷新,用户每天都要抽一定次数才能重新解锁。

//2.查询奖品配置
List<StrategyAwardEntity> strategyAwardEntities = raffleAward.queryRaffleStrategyAwardListByActivityId(request.getActivityId());
//3.获取规则配置
String[] treeIds = strategyAwardEntities.stream()
.map(StrategyAwardEntity::getRuleModels)
.filter(ruleModel -> ruleModel != null && !ruleModel.isEmpty())
.toArray(String[]::new);

//4.查询规则配置,获取奖品的解锁规则,抽奖N次后解锁
Map<String, Integer> ruleLockCountMap = raffleRule.queryAwardRuleLockCount(treeIds);
//5.查询抽奖次数 – 用户已经参与抽奖的次数
Integer dayPartakeCount = raffleActivityAccountQuotaService.queryRaffleActivityAccountDayPartakeCount(request.getActivityId(), request.getUserId());

三、前端对接

http://localhost:3000/?userId=xiaofuge&activityId=100301

缓存中也生成了相应的锁,并且有过期时间了:

赞(0)
未经允许不得转载:网硕互联帮助中心 » 大营销平台 —— 细节优化和前端对接
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!