提交 5dde2c13 编写于 作者: 鲸落和鲨掉's avatar 鲸落和鲨掉

提供抽奖API、活动预热装配以及活动的抽奖

上级 fc079a14
package cn.bugstack.trigger.api;
import cn.bugstack.trigger.api.dto.ActivityDrawRequestDTO;
import cn.bugstack.trigger.api.dto.ActivityDrawResponseDTO;
import cn.bugstack.types.model.Response;
/*
* @return
* @author zhaoyongfeng
* @description 抽奖活动服务
*/
public interface IRaffleActivityService {
/**
* 活动装配,数据预热缓存
* @param activityId 活动ID
* @return 装配结果
*/
Response<Boolean> armory(Long activityId);
/**
* 活动抽奖接口
* @param request 请求对象
* @return 返回结果
*/
Response<ActivityDrawResponseDTO> draw(ActivityDrawRequestDTO request);
}
......@@ -8,7 +8,7 @@ import cn.bugstack.types.model.Response;
import java.util.List;
public interface IRaffleService {
public interface IRaffleStrategyService {
/**
* 策略装配接口
......
package cn.bugstack.trigger.api.dto;
import lombok.Data;
/**
* @ClassName: ActivityDrawRequestDTO
* @Description: 活动抽奖请求对象
* @Author: zhaoyongfeng
* @Date: 2024/12/10 22:06
*/
@Data
public class ActivityDrawRequestDTO {
/**
* 用户ID
*/
private String userId;
/**
* 活动ID
*/
private Long activityId;
}
package cn.bugstack.trigger.api.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/*
* @return
* @author zhaoyongfeng
* @description 活动抽奖返回对象
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ActivityDrawResponseDTO {
// 奖品ID
private Integer awardId;
// 奖品标题
private String awardTitle;
// 排序编号【策略奖品配置的奖品顺序编号】
private Integer awardIndex;
}
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
/**
* 应用启动层,注意Application所在的包路径,是在上一层。这样才能扫描到其他 module
* */
package cn.bugstack;
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.bugstack.infrastructure.persistent.dao.IUserAwardRecordDao">
<insert id="insert" parameterType="cn.bugstack.infrastructure.persistent.po.UserAwardRecord"></insert>
</mapper>
......@@ -47,11 +47,20 @@
<update id="updateActivityAccountSubtractionQuota" parameterType="cn.bugstack.infrastructure.persistent.po.RaffleActivityAccount">
update raffle_activity_account
set total_count_surplus = total_count_surplus - 1,
day_count_surplus = day_count_surplus - 1,
month_count_surplus = month_count_surplus - 1,
update_time = now()
where user_id = #{userId} and activity_id = #{activityId} and total_count_surplus > 0 and day_count_surplus > 0 and month_count_surplus > 0
set total_count_surplus = total_count_surplus - 1, update_time = now()
where user_id = #{userId} and activity_id = #{activityId} and total_count_surplus > 0
</update>
<update id="updateActivityAccountMonthSubtractionQuota" parameterType="cn.bugstack.infrastructure.persistent.po.RaffleActivityAccount">
update raffle_activity_account
set month_count_surplus = month_count_surplus - 1, update_time = now()
where user_id = #{userId} and activity_id = #{activityId} and month_count_surplus > 0
</update>
<update id="updateActivityAccountDaySubtractionQuota" parameterType="cn.bugstack.infrastructure.persistent.po.RaffleActivityAccount">
update raffle_activity_account
set day_count_surplus = day_count_surplus - 1, update_time = now()
where user_id = #{userId} and activity_id = #{activityId} and day_count_surplus > 0
</update>
<update id="updateActivityAccountMonthSurplusImageQuota" parameterType="cn.bugstack.infrastructure.persistent.po.RaffleActivityAccount">
......
......@@ -20,5 +20,10 @@
from raffle_activity
where activity_id = #{activityId}
</select>
<select id="queryStrategyIdByActivityId" parameterType="java.lang.Long" resultType="java.lang.Long">
select strategy_id from raffle_activity where activity_id = #{activityId}
</select>
<select id="queryActivityIdByStrategyId" parameterType="java.lang.Long" resultType="java.lang.Long">
select activity_id from raffle_activity where strategy_id = #{strategyId}
</select>
</mapper>
......@@ -3,7 +3,7 @@
<mapper namespace="cn.bugstack.infrastructure.persistent.dao.IRaffleActivitySkuDao">
<resultMap id="dataMap" type="cn.bugstack.infrastructure.persistent.po.RaffleActivitySku">
<id column="id" property="id"/>
<result column="sku" property="sku"/>
<result column="activity_id" property="activityId"/>
<result column="activity_count_id" property="activityCountId"/>
......@@ -18,6 +18,9 @@
from raffle_activity_sku
where sku = #{sku}
</select>
<select id="queryActivitySkuListByActivityId" parameterType="java.lang.Long" resultMap="dataMap">
select sku, activity_count_id, stock_count, stock_count_surplus from raffle_activity_sku where activity_id = #{activityId}
</select>
<update id="updateActivitySkuStock" parameterType="java.lang.Long">
update raffle_activity_sku
......
......@@ -14,32 +14,31 @@
</resultMap>
<insert id="insert" parameterType="cn.bugstack.infrastructure.persistent.po.UserAwardRecord">
insert into task(user_id, topic, message_id, message, state, create_time, update_time)
values (#{userId}, #{topic}, #{messageId}, #{message}, #{state}, now(), now())
insert into task(
user_id, topic, message_id, message, state, create_time, update_time
) values (
#{userId},#{topic},#{messageId},#{message},#{state},now(),now()
)
</insert>
<update id="updateTaskSendMessageCompleted"
parameterType="cn.bugstack.infrastructure.persistent.po.UserAwardRecord">
update task
set state = 'completed',
update_time = now()
where user_id = #{userId}
and message_id = #{messageId}
set state = 'completed', update_time = now()
where user_id = #{userId} and message_id = #{messageId}
</update>
<update id="updateTaskSendMessageFail" parameterType="cn.bugstack.infrastructure.persistent.po.UserAwardRecord">
update task
set state = 'fail',
update_time = now()
where user_id = #{userId}
and message_id = #{messageId}
set state = 'fail', update_time = now()
where user_id = #{userId} and message_id = #{messageId}
</update>
<select id="queryNoSendMessageTaskList" resultMap="dataMap">
select user_id, topic, message_id, message
from task
where state = 'fail'
or (state = 'create' and now() - update_time > 6) limit 10
where state = 'fail' or (state = 'create' and now() - update_time > 6)
limit 10
</select>
</mapper>
......@@ -23,6 +23,11 @@
#{userId}, #{activityId}, #{activityName}, #{strategyId}, #{orderId}, #{orderTime}, #{orderState}, now(), now()
)
</insert>
<update id="updateUserRaffleOrderStateUsed" parameterType="cn.bugstack.infrastructure.persistent.po.UserRaffleOrder">
update user_raffle_order
set order_state = 'used', update_time = now()
where user_id = #{userId} and order_id = #{orderId} and order_state = 'create'
</update>
<select id="queryNoUsedRaffleOrder" parameterType="cn.bugstack.infrastructure.persistent.po.UserRaffleOrder" resultMap="dataMap">
select user_id, activity_id, activity_name, strategy_id, order_id, order_time, order_state
......
......@@ -32,7 +32,7 @@ public class AwardServiceTest {
*/
@Test
public void test_saveUserAwardRecord() throws InterruptedException {
for (int i = 0; i < 100; i++) {
for (int i = 0; i < 10; i++) {
UserAwardRecordEntity userAwardRecordEntity = new UserAwardRecordEntity();
userAwardRecordEntity.setUserId("zyf");
userAwardRecordEntity.setActivityId(100301L);
......
package cn.bugstack.domain.activity.model.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @ClassName: PartakeRaffleActivityEntity
......@@ -9,6 +12,9 @@ import lombok.Data;
* @Date: 2024/12/6 21:21
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PartakeRaffleActivityEntity {
/**
* 用户ID
......
......@@ -6,6 +6,7 @@ import cn.bugstack.domain.activity.model.entity.*;
import cn.bugstack.domain.activity.model.valobj.ActivitySkuStockKeyVO;
import java.util.Date;
import java.util.List;
/*
* @return
......@@ -46,4 +47,6 @@ public interface IActivityRepository {
ActivityAccountDayEntity queryActivityAccountDayByUserId(String userId, Long activityId, String day);
void saveCreatePartakeOrderAggregate(CreatePartakeOrderAggregate createPartakeOrderAggregate);
List<ActivitySkuEntity> queryActivitySkuListByActivityId(Long activityId);
}
......@@ -16,4 +16,13 @@ public interface IRaffleActivityPartakeService {
*/
UserRaffleOrderEntity createOrder(PartakeRaffleActivityEntity partakeRaffleActivityEntity);
/**
* 创建抽奖单;用户参与抽奖活动,扣减活动账户库存,产生抽奖单。如存在未被使用的抽奖单则直接返回已存在的抽奖单。
*
* @param userId 用户ID
* @param activityId 活动ID
* @return 用户抽奖订单实体对象
*/
UserRaffleOrderEntity createOrder(String userId, Long activityId);
}
......@@ -10,6 +10,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
......@@ -39,6 +40,21 @@ public class ActivityArmory implements IActivityArmory, IActivityDispatch {
return true;
}
@Override
public boolean assembleActivitySkuByActivityId(Long activityId) {
List<ActivitySkuEntity> activitySkuEntities = activityRepository.queryActivitySkuListByActivityId(activityId);
for (ActivitySkuEntity activitySkuEntity : activitySkuEntities) {
cacheActivitySkuStockCount(activitySkuEntity.getSku(), activitySkuEntity.getStockCountSurplus());
// 预热活动次数【查询时预热到缓存】
activityRepository.queryRaffleActivityCountByActivityCountId(activitySkuEntity.getActivityCountId());
}
// 预热活动【查询时预热到缓存】
activityRepository.queryRaffleActivityByActivityId(activityId);
return true;
}
private void cacheActivitySkuStockCount(Long sku, Integer stockCount) {
String cacheKey = Constants.RedisKey.ACTIVITY_SKU_STOCK_COUNT_KEY + sku;
activityRepository.cacheActivitySkuStockCount(cacheKey, stockCount);
......
......@@ -9,4 +9,5 @@ public interface IActivityArmory {
boolean assembleActivitySku(Long sku);
boolean assembleActivitySkuByActivityId(Long activityId);
}
......@@ -30,6 +30,13 @@ public abstract class AbstractRaffleActivityPartake implements IRaffleActivityPa
this.activityRepository = activityRepository;
}
@Override
public UserRaffleOrderEntity createOrder(String userId, Long activityId) {
return createOrder(PartakeRaffleActivityEntity.builder()
.userId(userId)
.activityId(activityId)
.build());
}
@Override
public UserRaffleOrderEntity createOrder(PartakeRaffleActivityEntity partakeRaffleActivityEntity) {
// 0. 基础信息
String userId = partakeRaffleActivityEntity.getUserId();
......@@ -71,6 +78,8 @@ public abstract class AbstractRaffleActivityPartake implements IRaffleActivityPa
return userRaffleOrder;
}
protected abstract CreatePartakeOrderAggregate doFilterAccount(String userId, Long activityId, Date currentDate);
protected abstract UserRaffleOrderEntity buildUserRaffleOrder(String userId, Long activityId, Date currentDate);
......
package cn.bugstack.domain.award.repository;
import cn.bugstack.domain.award.model.aggregate.UserAwardRecordAggregate;
/*
* @return 奖品仓储服务
* @author zhaoyongfeng
* @description 奖品仓储服务
*/
public interface IAwardRepository {
void saveUserAwardRecord(UserAwardRecordAggregate userAwardRecordAggregate);
}
......@@ -20,6 +20,7 @@ import javax.annotation.Resource;
@Service
public class AwardService implements IAwardService{
@Resource
private IAwardRepository awardRepository;
@Resource
private SendAwardMessageEvent sendAwardMessageEvent;
......
......@@ -18,6 +18,8 @@ public class RaffleAwardEntity {
/** 奖品ID */
private Integer awardId;
/** 抽奖奖品标题 */
private String awardTitle;
/** 奖品配置信息 */
private String awardConfig;
/** 奖品顺序号 */
......
......@@ -89,5 +89,19 @@ public interface IStrategyRepository {
* @return 奖品信息
*/
StrategyAwardEntity queryStrategyAwardEntity(Long strategyId, Integer awardId);
/**
* 查询策略ID
*
* @param activityId 活动ID
* @return 策略ID
*/
Long queryStrategyIdByActivityId(Long activityId);
/**
* 查询用户抽奖次数 - 当天的;策略ID:活动ID 1:1 的配置,可以直接用 strategyId 查询。
*
* @param userId 用户ID
* @param strategyId 策略ID
* @return 用户今日参与次数
*/
Integer queryTodayUserRaffleCount(String userId, Long strategyId);
}
......@@ -67,6 +67,7 @@ public abstract class AbstractRaffleStrategy implements IRaffleStrategy {
return RaffleAwardEntity.builder()
.awardId(awardId)
.awardConfig(awardConfig)
.awardTitle(strategyAward.getAwardTitle())
.sort(strategyAward.getSort())
.build();
}
......
......@@ -3,7 +3,7 @@ package cn.bugstack.domain.strategy.service;
import cn.bugstack.domain.strategy.model.valobj.StrategyAwardStockKeyVO;
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @author
* @description 抽奖库存相关服务,获取库存消耗队列
* @create 2024-02-09 12:17
*/
......
......@@ -14,5 +14,11 @@ public interface IStrategyArmory {
* @return 装配结果
*/
boolean assembleLotteryStrategy(Long strategyId);
/**
* 装配抽奖策略配置「触发的时机可以为活动审核通过后进行调用」
*
* @param activityId 活动ID
* @return 装配结果
*/
boolean assembleLotteryStrategyByActivityId(Long activityId);
}
......@@ -66,6 +66,12 @@ public class StrategyArmoryDispatch implements IStrategyArmory, IStrategyDispatc
return true;
}
@Override
public boolean assembleLotteryStrategyByActivityId(Long activityId) {
Long strategyId = repository.queryStrategyIdByActivityId(activityId);
return assembleLotteryStrategy(strategyId);
}
/**
* 计算公式;
* 1. 找到范围内最小的概率值,比如 0.1、0.02、0.003,需要找到的值是 0.003
......
......@@ -18,8 +18,8 @@ import javax.annotation.Resource;
@Component("rule_lock")
public class RuleLockLogicTreeNode implements ILogicTreeNode {
// 用户抽奖次数,后续完成这部分流程开发的时候,从数据库/Redis中读取
private Long userRaffleCount = 10L;
@Resource
private IStrategyRepository repository;
@Override
public DefaultTreeFactory.TreeActionEntity logic(String userId, Long strategyId, Integer awardId, String ruleValue) {
......@@ -32,6 +32,9 @@ public class RuleLockLogicTreeNode implements ILogicTreeNode {
throw new RuntimeException("规则过滤-次数锁异常 ruleValue: " + ruleValue + " 配置不正确");
}
// 查询用户抽奖次数 - 当天的;策略ID:活动ID 1:1 的配置,可以直接用 strategyId 查询。
Integer userRaffleCount = repository.queryTodayUserRaffleCount(userId, strategyId);
// 用户抽奖次数大于规则限定值,规则放行
if (userRaffleCount >= raffleCount) {
return DefaultTreeFactory.TreeActionEntity.builder()
......
......@@ -5,7 +5,7 @@ import cn.bugstack.domain.task.model.entity.TaskEntity;
import java.util.List;
/*
* @return
* @return 任务服务仓储接口
* @author zhaoyongfeng
* @description
*/
......
......@@ -5,9 +5,9 @@ import cn.bugstack.domain.task.model.entity.TaskEntity;
import java.util.List;
/*
* @return 消息任务服务接口
* @return
* @author zhaoyongfeng
* @description
* @description 消息任务服务接口
*/
public interface ITaskService {
/**
......
......@@ -3,6 +3,7 @@ package cn.bugstack.domain.task.service;
import cn.bugstack.domain.task.model.entity.TaskEntity;
import cn.bugstack.domain.task.repository.ITaskRepository;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
......@@ -13,6 +14,7 @@ import java.util.List;
* @Author: zhaoyongfeng
* @Date: 2024/12/9 17:07
*/
@Service
public class TaskService implements ITaskService{
@Resource
private ITaskRepository taskRepository;
......
......@@ -13,4 +13,7 @@ public interface IRaffleActivityDao {
RaffleActivity queryRaffleActivityByActivityId(Long activityId);
Long queryStrategyIdByActivityId(Long activityId);
Long queryActivityIdByStrategyId(Long strategyId);
}
......@@ -3,6 +3,8 @@ package cn.bugstack.infrastructure.persistent.dao;
import cn.bugstack.infrastructure.persistent.po.RaffleActivitySku;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 商品sku dao
......@@ -17,4 +19,5 @@ public interface IRaffleActivitySkuDao {
void clearActivitySkuStock(Long sku);
List<RaffleActivitySku> queryActivitySkuListByActivityId(Long activityId);
}
package cn.bugstack.infrastructure.persistent.dao;
import cn.bugstack.infrastructure.persistent.po.Task;
import cn.bugstack.middleware.db.router.annotation.DBRouter;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
......@@ -13,9 +14,9 @@ import java.util.List;
@Mapper
public interface ITaskDao {
void insert(Task task);
@DBRouter
void updateTaskSendMessageCompleted(Task task);
@DBRouter
void updateTaskSendMessageFail(Task task);
List<Task> queryNoSendMessageTaskList();
......
package cn.bugstack.infrastructure.persistent.dao;
import cn.bugstack.infrastructure.persistent.po.UserAwardRecord;
import cn.bugstack.middleware.db.router.annotation.DBRouter;
import cn.bugstack.middleware.db.router.annotation.DBRouterStrategy;
import org.apache.ibatis.annotations.Mapper;
......
......@@ -19,4 +19,5 @@ public interface IUserRaffleOrderDao {
@DBRouter
UserRaffleOrder queryNoUsedRaffleOrder(UserRaffleOrder userRaffleOrderReq);
int updateUserRaffleOrderStateUsed(UserRaffleOrder userRaffleOrderReq);
}
......@@ -5,6 +5,7 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
......@@ -18,6 +19,8 @@ import java.util.Date;
@NoArgsConstructor
public class RaffleActivityAccountDay {
private final SimpleDateFormat dateFormatDay = new SimpleDateFormat("yyyy-MM-dd");
/** 自增ID */
private String id;
/** 用户ID */
......@@ -34,5 +37,7 @@ public class RaffleActivityAccountDay {
private Date createTime;
/** 更新时间 */
private Date updateTime;
public String currentDay() {
return dateFormatDay.format(new Date());
}
}
......@@ -24,7 +24,9 @@ import org.springframework.stereotype.Repository;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
......@@ -427,4 +429,19 @@ public class ActivityRepository implements IActivityRepository {
}
}
@Override
public List<ActivitySkuEntity> queryActivitySkuListByActivityId(Long activityId) {
List<RaffleActivitySku> raffleActivitySkus = raffleActivitySkuDao.queryActivitySkuListByActivityId(activityId);
List<ActivitySkuEntity> activitySkuEntities = new ArrayList<>(raffleActivitySkus.size());
for (RaffleActivitySku raffleActivitySku:raffleActivitySkus){
ActivitySkuEntity activitySkuEntity = new ActivitySkuEntity();
activitySkuEntity.setSku(raffleActivitySku.getSku());
activitySkuEntity.setActivityCountId(raffleActivitySku.getActivityCountId());
activitySkuEntity.setStockCount(raffleActivitySku.getStockCount());
activitySkuEntity.setStockCountSurplus(raffleActivitySku.getStockCountSurplus());
activitySkuEntities.add(activitySkuEntity);
}
return activitySkuEntities;
}
}
......@@ -7,8 +7,10 @@ import cn.bugstack.domain.award.repository.IAwardRepository;
import cn.bugstack.infrastructure.event.EventPublisher;
import cn.bugstack.infrastructure.persistent.dao.ITaskDao;
import cn.bugstack.infrastructure.persistent.dao.IUserAwardRecordDao;
import cn.bugstack.infrastructure.persistent.dao.IUserRaffleOrderDao;
import cn.bugstack.infrastructure.persistent.po.Task;
import cn.bugstack.infrastructure.persistent.po.UserAwardRecord;
import cn.bugstack.infrastructure.persistent.po.UserRaffleOrder;
import cn.bugstack.middleware.db.router.strategy.IDBRouterStrategy;
import cn.bugstack.types.enums.ResponseCode;
import cn.bugstack.types.exception.AppException;
......@@ -22,7 +24,7 @@ import javax.annotation.Resource;
/**
* @ClassName: AwardRepository
* @Description:
* @Description: 奖品仓储服务
* @Author: zhaoyongfeng
* @Date: 2024/12/9 16:25
*/
......@@ -34,6 +36,8 @@ public class AwardRepository implements IAwardRepository {
@Resource
private IUserAwardRecordDao userAwardRecordDao;
@Resource
private IUserRaffleOrderDao userRaffleOrderDao;
@Resource
private IDBRouterStrategy dbRouter;
@Resource
private TransactionTemplate transactionTemplate;
......@@ -64,13 +68,26 @@ public class AwardRepository implements IAwardRepository {
task.setMessageId(taskEntity.getMessageId());
task.setMessage(JSON.toJSONString(taskEntity.getMessage()));
task.setState(taskEntity.getState().getCode());
UserRaffleOrder userRaffleOrderReq = new UserRaffleOrder();
userRaffleOrderReq.setUserId(userAwardRecordEntity.getUserId());
userRaffleOrderReq.setOrderId(userAwardRecordEntity.getOrderId());
try {
dbRouter.doRouter(userId);
transactionTemplate.execute(status -> {
try {
// 写入记录
userAwardRecordDao.insert(userAwardRecord);
// 写入任务
taskDao.insert(task);
return 1;
// 更新抽奖单
int count = userRaffleOrderDao.updateUserRaffleOrderStateUsed(userRaffleOrderReq);
if (1 != count) {
status.setRollbackOnly();
log.error("写入中奖记录,用户抽奖单已使用过,不可重复抽奖 userId: {} activityId: {} awardId: {}", userId, activityId, awardId);
throw new AppException(ResponseCode.ACTIVITY_ORDER_ERROR.getCode(), ResponseCode.ACTIVITY_ORDER_ERROR.getInfo());
}
return null;
} catch (DuplicateKeyException e) {
status.setRollbackOnly();
log.error("写入中奖记录,唯一索引冲突 userId: {} activityId: {} awardId: {}", userId, activityId, awardId, e);
......
package cn.bugstack.infrastructure.persistent.repository;
import cn.bugstack.domain.strategy.model.entity.StrategyAwardEntity;
import cn.bugstack.domain.strategy.model.entity.StrategyEntity;
import cn.bugstack.domain.strategy.model.entity.StrategyRuleEntity;
......@@ -25,12 +24,10 @@ import java.util.concurrent.TimeUnit;
import static cn.bugstack.types.enums.ResponseCode.UN_ASSEMBLED_STRATEGY_ARMORY;
/**
* @ClassName: StrategyRepository
* @Description: 策略服务仓储实现
* @Author: zhaoyongfeng
* @Date: 2024/11/14 19:27
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 策略服务仓储实现
* @create 2023-12-23 10:33
*/
@Slf4j
@Repository
......@@ -43,6 +40,8 @@ public class StrategyRepository implements IStrategyRepository {
@Resource
private IStrategyAwardDao strategyAwardDao;
@Resource
private IRaffleActivityDao raffleActivityDao;
@Resource
private IRedisService redisService;
@Resource
private IRuleTreeDao ruleTreeDao;
......@@ -50,7 +49,8 @@ public class StrategyRepository implements IStrategyRepository {
private IRuleTreeNodeDao ruleTreeNodeDao;
@Resource
private IRuleTreeNodeLineDao ruleTreeNodeLineDao;
@Resource
private IRaffleActivityAccountDayDao raffleActivityAccountDayDao;
@Override
public List<StrategyAwardEntity> queryStrategyAwardList(Long strategyId) {
......@@ -78,6 +78,13 @@ public class StrategyRepository implements IStrategyRepository {
return strategyAwardEntities;
}
/**
* 在 Redisson 中,当你调用 getMap 方法时,如果指定的 key 不存在,Redisson 并不会立即在 Redis 数据库中创建这个 key。相反,它会返回一个 RMap 对象的实例,这个实例是一个本地的 Java 对象,它代表了 Redis 中的一个哈希(hash)。
* <p>
* 当你开始使用这个 RMap 实例进行操作,比如添加键值对,那么 Redisson 会在 Redis 数据库中创建相应的 key,并将数据存储在这个 key 对应的哈希中。如果你只是获取了 RMap 实例而没有进行任何操作,那么在 Redis 数据库中是不会有任何变化的。
* <p>
* 简单来说,getMap 方法返回的 RMap 对象是懒加载的,只有在你实际进行操作时,Redis 数据库中的数据结构才会被创建或修改。
*/
@Override
public void storeStrategyAwardSearchRateTable(String key, Integer rateRange, Map<Integer, Integer> strategyAwardSearchRateTable) {
// 1. 存储抽奖策略范围值,如10000,用于生成1000以内的随机数
......@@ -106,7 +113,6 @@ public class StrategyRepository implements IStrategyRepository {
return redisService.getValue(cacheKey);
}
@Override
public StrategyEntity queryStrategyEntityByStrategyId(Long strategyId) {
// 优先从缓存获取
......@@ -114,6 +120,7 @@ public class StrategyRepository implements IStrategyRepository {
StrategyEntity strategyEntity = redisService.getValue(cacheKey);
if (null != strategyEntity) return strategyEntity;
Strategy strategy = strategyDao.queryStrategyByStrategyId(strategyId);
if (null == strategy) return StrategyEntity.builder().build();
strategyEntity = StrategyEntity.builder()
.strategyId(strategy.getStrategyId())
.strategyDesc(strategy.getStrategyDesc())
......@@ -129,6 +136,7 @@ public class StrategyRepository implements IStrategyRepository {
strategyRuleReq.setStrategyId(strategyId);
strategyRuleReq.setRuleModel(ruleModel);
StrategyRule strategyRuleRes = strategyRuleDao.queryStrategyRule(strategyRuleReq);
if (null == strategyRuleRes) return null;
return StrategyRuleEntity.builder()
.strategyId(strategyRuleRes.getStrategyId())
.awardId(strategyRuleRes.getAwardId())
......@@ -139,6 +147,11 @@ public class StrategyRepository implements IStrategyRepository {
.build();
}
@Override
public String queryStrategyRuleValue(Long strategyId, String ruleModel) {
return queryStrategyRuleValue(strategyId, null, ruleModel);
}
@Override
public String queryStrategyRuleValue(Long strategyId, Integer awardId, String ruleModel) {
StrategyRule strategyRule = new StrategyRule();
......@@ -148,12 +161,6 @@ public class StrategyRepository implements IStrategyRepository {
return strategyRuleDao.queryStrategyRuleValue(strategyRule);
}
@Override
public String queryStrategyRuleValue(Long strategyId, String ruleModel) {
return queryStrategyRuleValue(strategyId, null, ruleModel);
}
@Override
public StrategyAwardRuleModelVO queryStrategyAwardRuleModelVO(Long strategyId, Integer awardId) {
StrategyAward strategyAward = new StrategyAward();
......@@ -162,7 +169,6 @@ public class StrategyRepository implements IStrategyRepository {
String ruleModels = strategyAwardDao.queryStrategyAwardRuleModels(strategyAward);
if (null == ruleModels) return null;
return StrategyAwardRuleModelVO.builder().ruleModels(ruleModels).build();
}
@Override
......@@ -224,13 +230,12 @@ public class StrategyRepository implements IStrategyRepository {
redisService.setAtomicLong(cacheKey, awardCount);
}
@Override
public Boolean subtractionAwardStock(String cacheKey) {
long surplus = redisService.decr(cacheKey);
if (surplus < 0) {
// 库存小于0,恢复为0个
redisService.setValue(cacheKey, 0);
redisService.setAtomicLong(cacheKey, 0);
return false;
}
// 1. 按照cacheKey decr 后的值,如 99、98、97 和 key 组成为库存锁的key进行使用。
......@@ -249,7 +254,6 @@ public class StrategyRepository implements IStrategyRepository {
RBlockingQueue<StrategyAwardStockKeyVO> blockingQueue = redisService.getBlockingQueue(cacheKey);
RDelayedQueue<StrategyAwardStockKeyVO> delayedQueue = redisService.getDelayedQueue(blockingQueue);
delayedQueue.offer(strategyAwardStockKeyVO, 3, TimeUnit.SECONDS);
}
@Override
......@@ -257,7 +261,6 @@ public class StrategyRepository implements IStrategyRepository {
String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_QUERY_KEY;
RBlockingQueue<StrategyAwardStockKeyVO> destinationQueue = redisService.getBlockingQueue(cacheKey);
return destinationQueue.poll();
}
@Override
......@@ -266,7 +269,6 @@ public class StrategyRepository implements IStrategyRepository {
strategyAward.setStrategyId(strategyId);
strategyAward.setAwardId(awardId);
strategyAwardDao.updateStrategyAwardStock(strategyAward);
}
@Override
......@@ -297,8 +299,24 @@ public class StrategyRepository implements IStrategyRepository {
return strategyAwardEntity;
}
@Override
public Long queryStrategyIdByActivityId(Long activityId) {
return raffleActivityDao.queryStrategyIdByActivityId(activityId);
}
@Override
public Integer queryTodayUserRaffleCount(String userId, Long strategyId) {
// 活动ID
Long activityId = raffleActivityDao.queryActivityIdByStrategyId(strategyId);
// 封装参数
RaffleActivityAccountDay raffleActivityAccountDayReq = new RaffleActivityAccountDay();
raffleActivityAccountDayReq.setUserId(userId);
raffleActivityAccountDayReq.setActivityId(activityId);
raffleActivityAccountDayReq.setDay(raffleActivityAccountDayReq.currentDay());
RaffleActivityAccountDay raffleActivityAccountDay = raffleActivityAccountDayDao.queryActivityAccountDayByUserId(raffleActivityAccountDayReq);
if (null == raffleActivityAccountDay) return 0;
// 总次数 - 剩余的,等于今日参与的
return raffleActivityAccountDay.getDayCount() - raffleActivityAccountDay.getDayCountSurplus();
}
}
......@@ -18,7 +18,6 @@ import java.util.List;
* @Author: zhaoyongfeng
* @Date: 2024/12/9 17:12
*/
@Slf4j
@Repository
public class TaskRepository implements ITaskRepository {
@Resource
......@@ -26,7 +25,6 @@ public class TaskRepository implements ITaskRepository {
@Resource
private EventPublisher eventPublisher;
@Override
public void sendMessage(TaskEntity taskEntity) {
eventPublisher.publish(taskEntity.getTopic(), taskEntity.getMessage());
......
package cn.bugstack.trigger.http;
import cn.bugstack.domain.activity.model.entity.UserRaffleOrderEntity;
import cn.bugstack.domain.activity.service.IRaffleActivityPartakeService;
import cn.bugstack.domain.activity.service.armory.IActivityArmory;
import cn.bugstack.domain.award.model.entity.UserAwardRecordEntity;
import cn.bugstack.domain.award.model.valobj.AwardStateVO;
import cn.bugstack.domain.award.service.IAwardService;
import cn.bugstack.domain.strategy.model.entity.RaffleAwardEntity;
import cn.bugstack.domain.strategy.model.entity.RaffleFactorEntity;
import cn.bugstack.domain.strategy.service.IRaffleStrategy;
import cn.bugstack.domain.strategy.service.armory.IStrategyArmory;
import cn.bugstack.trigger.api.IRaffleActivityService;
import cn.bugstack.trigger.api.dto.ActivityDrawRequestDTO;
import cn.bugstack.trigger.api.dto.ActivityDrawResponseDTO;
import cn.bugstack.types.enums.ResponseCode;
import cn.bugstack.types.exception.AppException;
import cn.bugstack.types.model.Response;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.Date;
/**
* @ClassName: RaffleActivityController
* @Description:
* @Author: zhaoyongfeng
* @Date: 2024/12/10 22:11
*/
@Slf4j
@RestController()
@CrossOrigin("${app.config.cross-origin}")
@RequestMapping("/api/${app.config.api-version}/raffle/activity/")
public class RaffleActivityController implements IRaffleActivityService {
@Resource
private IRaffleActivityPartakeService raffleActivityPartakeService;
@Resource
private IActivityArmory activityArmory;
@Resource
private IStrategyArmory strategyArmory;
@Resource
private IRaffleStrategy raffleStrategy;
@Resource
private IAwardService awardService;
/**
* 活动装配 - 数据预热 | 把活动配置的对应的 sku 一起装配
*
* @param activityId 活动ID
* @return 装配结果
* <p>
* 接口:<a href="http://localhost:8091/api/v1/raffle/activity/armory">/api/v1/raffle/activity/armory</a>
* 入参:{"activityId":100001,"userId":"xiaofuge"}
*
* curl --request GET \
* --url 'http://localhost:8091/api/v1/raffle/activity/armory?activityId=100301'
*/
@RequestMapping(value = "armory",method = RequestMethod.GET)
@Override
public Response<Boolean> armory(@RequestParam Long activityId) {
try{
log.info("活动装配,数据预热,开始 activityId:{}", activityId);
// 1. 活动装配
activityArmory.assembleActivitySkuByActivityId(activityId);
// 2. 策略装配
strategyArmory.assembleLotteryStrategyByActivityId(activityId);
Response<Boolean> response = Response.<Boolean>builder()
.code(ResponseCode.SUCCESS.getCode())
.info(ResponseCode.SUCCESS.getInfo())
.data(true)
.build();
log.info("活动装配,数据预热,完成 activityId:{}", activityId);
return response;
}catch (Exception e){
log.error("活动装配,数据预热,失败 activityId:{}", activityId, e);
return Response.<Boolean>builder()
.code(ResponseCode.UN_ERROR.getCode())
.info(ResponseCode.UN_ERROR.getInfo())
.build();
}
}
/**
* 抽奖接口
*
* @param request 请求对象
* @return 抽奖结果
* <p>
* 接口:<a href="http://localhost:8091/api/v1/raffle/activity/draw">/api/v1/raffle/activity/draw</a>
* 入参:{"activityId":100001,"userId":"xiaofuge"}
*
* curl --request POST \
* --url http://localhost:8091/api/v1/raffle/activity/draw \
* --header 'content-type: application/json' \
* --data '{
* "userId":"xiaofuge",
* "activityId": 100301
* }'
*/
@RequestMapping(value = "draw",method = RequestMethod.POST)
@Override
public Response<ActivityDrawResponseDTO> draw(@RequestBody ActivityDrawRequestDTO request) {
try{
log.info("活动抽奖 userId:{} activityId:{}", request.getUserId(), request.getActivityId());
// 1. 参数校验
if (StringUtils.isBlank(request.getUserId()) || null == request.getActivityId()) {
throw new AppException(ResponseCode.ILLEGAL_PARAMETER.getCode(), ResponseCode.ILLEGAL_PARAMETER.getInfo());
}
// 2. 参与活动 - 创建参与记录订单
UserRaffleOrderEntity orderEntity = raffleActivityPartakeService.createOrder(request.getUserId(), request.getActivityId());
log.info("活动抽奖,创建订单 userId:{} activityId:{} orderId:{}", request.getUserId(), request.getActivityId(), orderEntity.getOrderId());
// 3. 抽奖策略 - 执行抽奖
RaffleAwardEntity raffleAwardEntity = raffleStrategy.performRaffle(RaffleFactorEntity.builder()
.userId(orderEntity.getUserId())
.strategyId(orderEntity.getStrategyId())
.build());
// 4. 存放结果 - 写入中奖记录
UserAwardRecordEntity userAwardRecord = UserAwardRecordEntity.builder()
.userId(orderEntity.getUserId())
.activityId(orderEntity.getActivityId())
.strategyId(orderEntity.getStrategyId())
.orderId(orderEntity.getOrderId())
.awardId(raffleAwardEntity.getAwardId())
.awardTitle(raffleAwardEntity.getAwardTitle())
.awardTime(new Date())
.awardState(AwardStateVO.create)
.build();
awardService.saveUserAwardRecord(userAwardRecord);
// 5. 返回结果
return Response.<ActivityDrawResponseDTO>builder()
.code(ResponseCode.SUCCESS.getCode())
.info(ResponseCode.SUCCESS.getInfo())
.data(ActivityDrawResponseDTO.builder()
.awardId(raffleAwardEntity.getAwardId())
.awardTitle(raffleAwardEntity.getAwardTitle())
.awardIndex(raffleAwardEntity.getSort())
.build())
.build();
}catch(AppException e){
log.error("活动抽奖失败 userId:{} activityId:{}", request.getUserId(), request.getActivityId(), e);
return Response.<ActivityDrawResponseDTO>builder()
.code(e.getCode())
.info(e.getInfo())
.build();
}catch (Exception e) {
log.error("活动抽奖失败 userId:{} activityId:{}", request.getUserId(), request.getActivityId(), e);
return Response.<ActivityDrawResponseDTO>builder()
.code(ResponseCode.UN_ERROR.getCode())
.info(ResponseCode.UN_ERROR.getInfo())
.build();
}
}
}
......@@ -6,7 +6,7 @@ import cn.bugstack.domain.strategy.model.entity.StrategyAwardEntity;
import cn.bugstack.domain.strategy.service.IRaffleAward;
import cn.bugstack.domain.strategy.service.IRaffleStrategy;
import cn.bugstack.domain.strategy.service.armory.IStrategyArmory;
import cn.bugstack.trigger.api.IRaffleService;
import cn.bugstack.trigger.api.IRaffleStrategyService;
import cn.bugstack.trigger.api.dto.RaffleAwardListRequestDTO;
import cn.bugstack.trigger.api.dto.RaffleRequestDTO;
import cn.bugstack.trigger.api.dto.RaffleAwardListResponseDTO;
......@@ -23,7 +23,7 @@ import java.util.ArrayList;
import java.util.List;
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @author
* @description 营销抽奖服务
* @create 2024-02-14 09:21
*/
......@@ -31,7 +31,7 @@ import java.util.List;
@RestController()
@CrossOrigin("${app.config.cross-origin}")
@RequestMapping("/api/${app.config.api-version}/raffle/")
public class RaffleController implements IRaffleService {
public class RaffleStrategyController implements IRaffleStrategyService {
@Resource
private IRaffleAward raffleAward;
......
......@@ -20,7 +20,7 @@ public class UpdateActivitySkuStockJob {
@Resource
private IRaffleActivitySkuStockService skuStock;
@Scheduled(cron = "0/5 * * * * ?")
@Scheduled(cron = "0 * * * * ?")
public void exec() {
try {
log.info("定时任务,更新活动sku库存【延迟队列获取,降低对数据库的更新频次,不要产生竞争】");
......
......@@ -20,7 +20,7 @@ public class UpdateAwardStockJob {
@Resource
private IRaffleStock raffleStock;
@Scheduled(cron = "0/5 * * * * ?")
@Scheduled(cron = "0 * * * * ?")
public void exec() {
try {
log.info("定时任务,更新奖品消耗库存【延迟队列获取,降低对数据库的更新频次,不要产生竞争】");
......
package cn.bugstack.trigger.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @ClassName: SendAwardCustomer
* @Description: 用户奖品记录消息消费者
* @Author: zhaoyongfeng
* @Date: 2024/12/10 16:47
*/
@Slf4j
@Component
public class SendAwardCustomer {
@Value("${spring.rabbitmq.topic.send_award}")
private String topic;
@RabbitListener(queuesToDeclare = @Queue(value = "${spring.rabbitmq.topic.send_award}"))
public void listener(String message) {
try {
log.info("监听用户奖品发送消息 topic: {} message: {}", topic, message);
} catch (Exception e) {
log.error("监听用户奖品发送消息,消费失败 topic: {} message: {}", topic, message);
throw e;
}
}
}
\ No newline at end of file
......@@ -22,6 +22,7 @@ public enum ResponseCode {
ACCOUNT_QUOTA_ERROR("ERR_BIZ_006","账户总额度不足"),
ACCOUNT_MONTH_QUOTA_ERROR("ERR_BIZ_007","账户月额度不足"),
ACCOUNT_DAY_QUOTA_ERROR("ERR_BIZ_008","账户日额度不足"),
ACTIVITY_ORDER_ERROR("ERR_BIZ_009", "用户抽奖单已使用过,不可重复抽奖"),
;
......
此差异已折叠。
此差异已折叠。
......@@ -14,7 +14,7 @@ java.lang.NullPointerException: null
at cn.bugstack.domain.strategy.service.rule.chain.factory.DefaultChainFactory.openLogicChain(DefaultChainFactory.java:35)
at cn.bugstack.domain.strategy.service.raffle.DefaultRaffleStrategy.raffleLogicChain(DefaultRaffleStrategy.java:37)
at cn.bugstack.domain.strategy.AbstractRaffleStrategy.performRaffle(AbstractRaffleStrategy.java:50)
at cn.bugstack.trigger.http.RaffleController.randomRaffle(RaffleController.java:128)
at cn.bugstack.trigger.http.RaffleStrategyController.randomRaffle(RaffleController.java:128)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
......
24-11-24.23:59:54.866 [main ] INFO Application - Starting Application v1.0-SNAPSHOT using Java 1.8.0_342 on 9b0494bbcd7f with PID 7 (/big-market-app.jar started by root in /)
24-11-24.23:59:54.870 [main ] INFO Application - The following 1 profile is active: "dev"
24-11-24.23:59:55.852 [main ] INFO RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode
24-11-24.23:59:55.855 [main ] INFO RepositoryConfigurationDelegate - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
24-11-24.23:59:55.881 [main ] INFO RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 10 ms. Found 0 Redis repository interfaces.
24-11-24.23:59:56.989 [main ] INFO TomcatWebServer - Tomcat initialized with port(s): 8091 (http)
24-11-24.23:59:57.002 [main ] INFO Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8091"]
24-11-24.23:59:57.004 [main ] INFO StandardService - Starting service [Tomcat]
24-11-24.23:59:57.004 [main ] INFO StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.75]
24-11-24.23:59:57.093 [main ] INFO [/] - Initializing Spring embedded WebApplicationContext
24-11-24.23:59:57.093 [main ] INFO ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 2163 ms
24-11-24.23:59:57.934 [main ] INFO Version - Redisson 3.23.4
24-11-24.23:59:58.337 [redisson-netty-2-6] INFO MasterPubSubConnectionPool - 1 connections initialized for redis/172.19.0.2:6379
24-11-24.23:59:58.456 [redisson-netty-2-14] INFO MasterConnectionPool - 5 connections initialized for redis/172.19.0.2:6379
24-11-24.23:59:59.368 [main ] INFO EndpointLinksResolver - Exposing 1 endpoint(s) beneath base path '/actuator'
24-11-24.23:59:59.533 [main ] INFO Http11NioProtocol - Starting ProtocolHandler ["http-nio-8091"]
24-11-24.23:59:59.566 [main ] INFO TomcatWebServer - Tomcat started on port(s): 8091 (http) with context path ''
24-11-24.23:59:59.589 [main ] INFO Application - Started Application in 5.334 seconds (JVM running for 5.998)
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册