提交 7a1b7a50 编写于 作者: H HappyChan

Merge branch '20220405_happy_redis' into 20220414_happy_erp_intf

# Conflicts:
#	lottery-domain/src/main/java/cn/happy/lottery/domain/activity/repository/IActivityRepository.java
#	lottery-infrastructure/src/main/java/cn/happy/lottery/infrastructure/dao/IActivityDao.java
#	lottery-infrastructure/src/main/java/cn/happy/lottery/infrastructure/repository/ActivityRepository.java
#	lottery-interfaces/src/main/resources/mybatis/mapper/Activity_Mapper.xml
package cn.happy.lottery.application.mq.consumer;
import cn.happy.lottery.domain.activity.model.vo.ActivityPartakeRecordVO;
import cn.happy.lottery.domain.activity.service.partake.IActivityPartake;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.support.Acknowledgment;
import org.springframework.kafka.support.KafkaHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Optional;
/**
* @author: happy
* @date: 2022/4/6
* @description: 抽奖活动领取记录监听消息
*/
@Component
public class LotteryActivityPartakeRecordListener {
private Logger logger = LoggerFactory.getLogger(LotteryActivityPartakeRecordListener.class);
@Resource
private IActivityPartake activityPartake;
@KafkaListener(topics = "lottery_activity_partake", groupId = "lottery")
public void onMessage(ConsumerRecord<?, ?> record, Acknowledgment ack, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
Optional<?> message = Optional.ofNullable(record.value());
// 1. 判断消息是否存在
if (!message.isPresent()) {
return;
}
// 2. 转化对象(或者你也可以重写Serializer<T>)
ActivityPartakeRecordVO activityPartakeRecordVO = JSON.parseObject((String) message.get(), ActivityPartakeRecordVO.class);
logger.info("消费MQ消息,异步扣减活动库存 message: {}", message.get());
// 3. 更新数据库库存【实际场景业务体量较大,可能也会由于MQ消费引起并发,对数据库产生压力,所以如果并发量较大,可以把库存记录缓存中,并使用定时任务进行处理缓存和数据库库存同步,减少对数据库的操作次数】
activityPartake.updateActivityStock(activityPartakeRecordVO);
}
}
package cn.happy.lottery.application.mq.producer;
import cn.happy.lottery.domain.activity.model.vo.ActivityPartakeRecordVO;
import cn.happy.lottery.domain.activity.model.vo.InvoiceVO;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
......@@ -33,6 +34,14 @@ public class KafkaProducer {
*/
public static final String TOPIC_INVOICE = "lottery_invoice";
/**
* MQ主题:活动领取记录
*
* 创建topic:bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic lottery_activity_partake
*/
public static final String TOPIC_ACTIVITY_PARTAKE = "lottery_activity_partake";
/**
* 发送中奖物品发货单消息
*
......@@ -40,9 +49,19 @@ public class KafkaProducer {
*/
public ListenableFuture<SendResult<String, Object>> sendLotteryInvoice(InvoiceVO invoice) {
String objJson = JSON.toJSONString(invoice);
logger.info("发送MQ消息 topic:{} bizId:{} message:{}", TOPIC_INVOICE, invoice.getuId(), objJson);
logger.info("发送MQ消息(中奖发货单) topic:{} bizId:{} message:{}", TOPIC_INVOICE, invoice.getuId(), objJson);
return kafkaTemplate.send(TOPIC_INVOICE, objJson);
}
/**
* 发送领取活动记录MQ
*
* @param activityPartakeRecord 领取活动记录
*/
public ListenableFuture<SendResult<String, Object>> sendLotteryActivityPartakeRecord(ActivityPartakeRecordVO activityPartakeRecord) {
String objJson = JSON.toJSONString(activityPartakeRecord);
logger.info("发送MQ消息(领取活动记录) topic:{} bizId:{} message:{}", TOPIC_ACTIVITY_PARTAKE, activityPartakeRecord.getuId(), objJson);
return kafkaTemplate.send(TOPIC_ACTIVITY_PARTAKE, objJson);
}
}
......@@ -9,6 +9,7 @@ import cn.happy.lottery.common.Constants;
import cn.happy.lottery.common.Result;
import cn.happy.lottery.domain.activity.model.req.PartakeReq;
import cn.happy.lottery.domain.activity.model.res.PartakeResult;
import cn.happy.lottery.domain.activity.model.vo.ActivityPartakeRecordVO;
import cn.happy.lottery.domain.activity.model.vo.DrawOrderVO;
import cn.happy.lottery.domain.activity.model.vo.InvoiceVO;
import cn.happy.lottery.domain.activity.service.partake.IActivityPartake;
......@@ -60,44 +61,55 @@ public class ActivityProcessImpl implements IActivityDrawProcess {
// 1. 领取活动
PartakeResult partakeResult = activityPartake.doPartake(new PartakeReq(req.getuId(), req.getActivityId()));
if (!Constants.ResponseCode.SUCCESS.getCode().equals(partakeResult.getCode())) {
if (!Constants.ResponseCode.SUCCESS.getCode().equals(partakeResult.getCode()) && !Constants.ResponseCode.NOT_CONSUMED_TAKE.getCode().equals(partakeResult.getCode())) {
return new DrawProcessResult(partakeResult.getCode(), partakeResult.getInfo());
}
// 2. 首次成功领取活动,发送 MQ 消息
if (Constants.ResponseCode.SUCCESS.getCode().equals(partakeResult.getCode())) {
ActivityPartakeRecordVO activityPartakeRecord = new ActivityPartakeRecordVO();
activityPartakeRecord.setuId(req.getuId());
activityPartakeRecord.setActivityId(req.getActivityId());
activityPartakeRecord.setStockCount(partakeResult.getStockCount());
activityPartakeRecord.setStockSurplusCount(partakeResult.getStockSurplusCount());
// 发送 MQ 消息
kafkaProducer.sendLotteryActivityPartakeRecord(activityPartakeRecord);
}
Long strategyId = partakeResult.getStrategyId();
Long takeId = partakeResult.getTakeId();
// 2. 执行抽奖
// 3. 执行抽奖
DrawResult drawResult = drawExec.doDrawExec(new DrawReq(req.getuId(), strategyId));
if (Constants.DrawState.FAIL.getCode().equals(drawResult.getDrawState())) {
return new DrawProcessResult(Constants.ResponseCode.LOSING_DRAW.getCode(), Constants.ResponseCode.LOSING_DRAW.getInfo());
}
// 3. 结果落库
// 4. 结果落库
DrawOrderVO drawOrderVO = buildDrawOrderVO(req, strategyId, takeId, drawResult.getDrawAwardInfo());
Result recordResult = activityPartake.recordDrawOrder(drawOrderVO);
if (!Constants.ResponseCode.SUCCESS.getCode().equals(recordResult.getCode())) {
return new DrawProcessResult(recordResult.getCode(), recordResult.getInfo());
}
// 4. 发送MQ,触发发奖流程
// 5. 发送MQ,触发发奖流程
InvoiceVO invoiceVO = buildInvoiceVO(drawOrderVO);
ListenableFuture<SendResult<String, Object>> future = kafkaProducer.sendLotteryInvoice(invoiceVO);
future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {
@Override
public void onSuccess(SendResult<String, Object> result) {
// 4.1 MQ 消息发送完成,更新数据库表 user_strategy_export.mq_state = 1
// 5.1 MQ 消息发送完成,更新数据库表 user_strategy_export.mq_state = 1
activityPartake.updateInvoiceMqState(invoiceVO.getuId(), invoiceVO.getOrderId(), Constants.MQState.COMPLETE.getCode());
}
@Override
public void onFailure(Throwable ex) {
// 4.2 MQ 消息发送失败,更新数据库表 user_strategy_export.mq_state = 2 【等待定时任务扫码补偿MQ消息】
// 5.2 MQ 消息发送失败,更新数据库表 user_strategy_export.mq_state = 2 【等待定时任务扫码补偿MQ消息】
activityPartake.updateInvoiceMqState(invoiceVO.getuId(), invoiceVO.getOrderId(), Constants.MQState.FAIL.getCode());
}
});
// 5. 返回结果
// 6. 返回结果
return new DrawProcessResult(Constants.ResponseCode.SUCCESS.getCode(), Constants.ResponseCode.SUCCESS.getInfo(), drawResult.getDrawAwardInfo());
}
......
......@@ -8,12 +8,15 @@ public class Constants {
public enum ResponseCode{
SUCCESS("0000", "成功"),
UN_ERROR("0001","未知失败"),
ILLEGAL_PARAMETER("0002","非法参数"),
UN_ERROR("0001", "未知失败"),
ILLEGAL_PARAMETER("0002", "非法参数"),
INDEX_DUP("0003", "主键冲突"),
NO_UPDATE("0004","SQL操作无更新"),
NO_UPDATE("0004", "SQL操作无更新"),
LOSING_DRAW("D001", "未中奖"),
RULE_ERR("D002", "量化人群规则执行失败");
RULE_ERR("D002", "量化人群规则执行失败"),
NOT_CONSUMED_TAKE("D003", "未消费活动领取记录"),
OUT_OF_STOCK("D004", "活动无库存"),
ERR_TOKEN("D005", "分布式锁失败");
private String code;
private String info;
......@@ -42,6 +45,27 @@ public class Constants {
public static final Long TREE_NULL_NODE = 0L;
}
/**
* 缓存 Key
*/
public static final class RedisKey {
// 抽奖活动库存 Key
private static final String LOTTERY_ACTIVITY_STOCK_COUNT = "lottery_activity_stock_count_";
public static String KEY_LOTTERY_ACTIVITY_STOCK_COUNT(Long activityId) {
return LOTTERY_ACTIVITY_STOCK_COUNT + activityId;
}
// 抽奖活动库存锁 Key
private static final String LOTTERY_ACTIVITY_STOCK_COUNT_TOKEN = "lottery_activity_stock_count_token_";
public static String KEY_LOTTERY_ACTIVITY_STOCK_COUNT_TOKEN(Long activityId, Integer stockUsedCount) {
return LOTTERY_ACTIVITY_STOCK_COUNT_TOKEN + activityId + "_" + stockUsedCount;
}
}
/**
* 决策树节点
*/
......
......@@ -53,6 +53,14 @@
<groupId>cn.happy</groupId>
<artifactId>db-router-happy</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
<artifactId>lottery-domain</artifactId>
......
......@@ -9,15 +9,30 @@ import cn.happy.lottery.common.Result;
*/
public class PartakeResult extends Result {
/**
* 策略ID
*/
/** 策略ID */
private Long strategyId;
/**
* 活动领取ID
*/
/** 活动领取ID */
private Long takeId;
/** 库存 */
private Integer stockCount;
/** activity 库存剩余 */
private Integer stockSurplusCount;
public Integer getStockCount() {
return stockCount;
}
public void setStockCount(Integer stockCount) {
this.stockCount = stockCount;
}
public Integer getStockSurplusCount() {
return stockSurplusCount;
}
public void setStockSurplusCount(Integer stockSurplusCount) {
this.stockSurplusCount = stockSurplusCount;
}
public PartakeResult(String code, String info) {
super(code, info);
......
package cn.happy.lottery.domain.activity.model.res;
import cn.happy.lottery.common.Result;
/**
* @author: happy
* @date: 2022/4/6
* @description: 库存处理结果
*/
public class StockResult extends Result {
/**
* 库存 Key
*/
private String stockKey;
/**
* activity 库存剩余
*/
private Integer stockSurplusCount;
public StockResult(String code, String info) {
super(code, info);
}
public StockResult(String code, String info, String stockKey, Integer stockSurplusCount) {
super(code, info);
this.stockKey = stockKey;
this.stockSurplusCount = stockSurplusCount;
}
public String getStockKey() {
return stockKey;
}
public void setStockKey(String stockKey) {
this.stockKey = stockKey;
}
public Integer getStockSurplusCount() {
return stockSurplusCount;
}
public void setStockSurplusCount(Integer stockSurplusCount) {
this.stockSurplusCount = stockSurplusCount;
}
}
......@@ -35,6 +35,11 @@ public class ActivityBillVO {
*/
private Date endDateTime;
/**
* 库存
*/
private Integer stockCount;
/**
* 库存剩余
*/
......@@ -101,6 +106,14 @@ public class ActivityBillVO {
this.endDateTime = endDateTime;
}
public Integer getStockCount() {
return stockCount;
}
public void setStockCount(Integer stockCount) {
this.stockCount = stockCount;
}
public Integer getStockSurplusCount() {
return stockSurplusCount;
}
......@@ -149,6 +162,7 @@ public class ActivityBillVO {
", activityName='" + activityName + '\'' +
", beginDateTime=" + beginDateTime +
", endDateTime=" + endDateTime +
", stockCount=" + stockCount +
", stockSurplusCount=" + stockSurplusCount +
", state=" + state +
", strategyId=" + strategyId +
......
package cn.happy.lottery.domain.activity.model.vo;
/**
* @description: 活动参与记录
* @author: happy
* @date: 2022/04/06
*/
public class ActivityPartakeRecordVO {
/** 用户ID */
private String uId;
/** activity 活动ID */
private Long activityId;
/** 库存 */
private Integer stockCount;
/** activity 库存剩余 */
private Integer stockSurplusCount;
public String getuId() {
return uId;
}
public void setuId(String uId) {
this.uId = uId;
}
public Long getActivityId() {
return activityId;
}
public void setActivityId(Long activityId) {
this.activityId = activityId;
}
public Integer getStockCount() {
return stockCount;
}
public void setStockCount(Integer stockCount) {
this.stockCount = stockCount;
}
public Integer getStockSurplusCount() {
return stockSurplusCount;
}
public void setStockSurplusCount(Integer stockSurplusCount) {
this.stockSurplusCount = stockSurplusCount;
}
@Override
public String toString() {
return "ActivityPartakeRecordVO{" +
"uId='" + uId + '\'' +
", activityId=" + activityId +
", stockCount=" + stockCount +
", stockSurplusCount=" + stockSurplusCount +
'}';
}
}
package cn.happy.lottery.domain.activity.repository;
import cn.happy.lottery.common.Constants;
import cn.happy.lottery.domain.activity.model.aggregates.ActivityInfoLimitPageRich;
import cn.happy.lottery.domain.activity.model.req.ActivityInfoLimitPageReq;
import cn.happy.lottery.domain.activity.model.req.PartakeReq;
import cn.happy.lottery.domain.activity.model.res.StockResult;
import cn.happy.lottery.domain.activity.model.vo.*;
import cn.happy.lottery.domain.activity.service.stateflow.ActivityStateEnum;
......@@ -17,7 +16,7 @@ import java.util.List;
public interface IActivityRepository {
/**
* 配置
* 添加活动配置
* @param activity
*/
void addActivity(ActivityVO activity);
......@@ -106,4 +105,25 @@ public interface IActivityRepository {
* @param activityVO
*/
void updateActivity(ActivityVO activityVO);
/**
* 扣减活动库存,通过Redis
*
* @param uId 用户ID
* @param activityId 活动ID
* @param stockCount 总库存
* @return 扣减结果
*/
StockResult subtractionActivityStockByRedis(String uId, Long activityId, Integer stockCount);
/**
* 恢复活动库存,通过Redis 【如果非常异常,则需要进行缓存库存恢复,只保证不超卖的特性,所以不保证一定能恢复占用库存,另外最终可以由任务进行补偿库存】
*
* @param activityId 活动ID
* @param tokenKey 分布式 KEY 用于清理
* @param code 状态
*/
void recoverActivityCacheStockByRedis(Long activityId, String tokenKey, String code);
}
package cn.happy.lottery.domain.activity.repository;
import cn.happy.lottery.domain.activity.model.vo.ActivityPartakeRecordVO;
import cn.happy.lottery.domain.activity.model.vo.DrawOrderVO;
import cn.happy.lottery.domain.activity.model.vo.InvoiceVO;
import cn.happy.lottery.domain.activity.model.vo.UserTakeActivityVO;
......@@ -83,4 +84,10 @@ public interface IUserTakeActivityRepository {
*/
List<InvoiceVO> scanInvoiceMqState();
/**
* 更新活动库存
*
* @param activityPartakeRecordVO 活动领取记录
*/
void updateActivityStock(ActivityPartakeRecordVO activityPartakeRecordVO);
}
......@@ -4,6 +4,7 @@ import cn.happy.lottery.common.Constants;
import cn.happy.lottery.common.Result;
import cn.happy.lottery.domain.activity.model.req.PartakeReq;
import cn.happy.lottery.domain.activity.model.res.PartakeResult;
import cn.happy.lottery.domain.activity.model.res.StockResult;
import cn.happy.lottery.domain.activity.model.vo.ActivityBillVO;
import cn.happy.lottery.domain.activity.model.vo.UserTakeActivityVO;
import cn.happy.lottery.domain.support.ids.IIdGenerator;
......@@ -27,7 +28,7 @@ public abstract class BaseActivityPartake extends ActivityPartakeSupport impleme
// 1. 查询是否存在未执行抽奖的领取活动单【user_take_activity 存在 state = 0,领取了但抽奖过程失败的,可以直接返回领取结果继续抽奖】
UserTakeActivityVO userTakeActivityVO = this.queryNoConsumedTakeActivityOrder(req.getActivityId(), req.getuId());
if (null != userTakeActivityVO) {
return buildPartakeResult(userTakeActivityVO.getStrategyId(), userTakeActivityVO.getTakeId());
return buildPartakeResult(userTakeActivityVO.getStrategyId(), userTakeActivityVO.getTakeId(), Constants.ResponseCode.NOT_CONSUMED_TAKE);
}
// 2. 查询活动账单
......@@ -39,9 +40,10 @@ public abstract class BaseActivityPartake extends ActivityPartakeSupport impleme
return new PartakeResult(checkResult.getCode(), checkResult.getInfo());
}
// 4. 扣减活动库存【目前为直接对配置库中的 lottery.activity 直接操作表扣减库存,后续优化为Redis扣减】
Result subtractionActivityResult = this.subtractionActivityStock(req);
// 4. 扣减活动库存,通过 Redis【活动库存扣减编号,作为锁的 key,缩小锁粒度】 Begin
StockResult subtractionActivityResult = subtractionActivityStockByRedis(req.getuId(), req.getActivityId(), activityBillVO.getStockCount());
if (!Constants.ResponseCode.SUCCESS.getCode().equals(subtractionActivityResult.getCode())) {
this.recoverActivityCacheStockByRedis(req.getActivityId(), subtractionActivityResult.getStockKey(), subtractionActivityResult.getCode());
return new PartakeResult(subtractionActivityResult.getCode(), subtractionActivityResult.getInfo());
}
......@@ -49,11 +51,34 @@ public abstract class BaseActivityPartake extends ActivityPartakeSupport impleme
Long takeId = idGeneratorMap.get(Constants.Ids.SnowFlake).nextId();
Result grabResult = this.grabActivity(req, activityBillVO, takeId);
if (!Constants.ResponseCode.SUCCESS.getCode().equals(grabResult.getCode())) {
this.recoverActivityCacheStockByRedis(req.getActivityId(), subtractionActivityResult.getStockKey(), grabResult.getCode());
return new PartakeResult(grabResult.getCode(), grabResult.getInfo());
}
// 6.封装结果【返回的策略ID,用于继续完成抽奖步骤】
return buildPartakeResult(activityBillVO.getStrategyId(), takeId);
// 6. 扣减活动库存,通过 Redis End
this.recoverActivityCacheStockByRedis(req.getActivityId(), subtractionActivityResult.getStockKey(), Constants.ResponseCode.SUCCESS.getCode());
// 7.封装结果【返回的策略ID,用于继续完成抽奖步骤】
return buildPartakeResult(activityBillVO.getStrategyId(), takeId, activityBillVO.getStockCount(), subtractionActivityResult.getStockSurplusCount(), Constants.ResponseCode.SUCCESS);
}
/**
* 封装结果【返回的策略ID,用于继续完成抽奖步骤】
*
* @param strategyId 策略ID
* @param takeId 领取ID
* @param stockCount 库存
* @param stockSurplusCount 剩余库存
* @param code 状态码
* @return 封装结果
*/
private PartakeResult buildPartakeResult(Long strategyId, Long takeId, Integer stockCount, Integer stockSurplusCount, Constants.ResponseCode code) {
PartakeResult partakeResult = new PartakeResult(code.getCode(), code.getInfo());
partakeResult.setStrategyId(strategyId);
partakeResult.setTakeId(takeId);
partakeResult.setStockCount(stockCount);
partakeResult.setStockSurplusCount(stockSurplusCount);
return partakeResult;
}
/**
......@@ -61,15 +86,35 @@ public abstract class BaseActivityPartake extends ActivityPartakeSupport impleme
*
* @param strategyId 策略ID
* @param takeId 领取ID
* @param code 状态码
* @return 封装结果
*/
private PartakeResult buildPartakeResult(Long strategyId, Long takeId) {
PartakeResult partakeResult = new PartakeResult(Constants.ResponseCode.SUCCESS.getCode(), Constants.ResponseCode.SUCCESS.getInfo());
private PartakeResult buildPartakeResult(Long strategyId, Long takeId, Constants.ResponseCode code) {
PartakeResult partakeResult = new PartakeResult(code.getCode(), code.getInfo());
partakeResult.setStrategyId(strategyId);
partakeResult.setTakeId(takeId);
return partakeResult;
}
/**
* 扣减活动库存,通过 Redis
*
* @param uId
* @param activityId
* @param stockCount
* @return
*/
protected abstract StockResult subtractionActivityStockByRedis(String uId, Long activityId, Integer stockCount);
/**
* 恢复活动库存,通过 Redis 【如果非常异常,则要进行缓存库存恢复,只保证不超卖的特性,所以不保证一定能恢复占用库存,另外最终可以由任务进行补偿库存】
*
* @param activityId
* @param tokenKey
* @param code
*/
protected abstract void recoverActivityCacheStockByRedis(Long activityId, String tokenKey, String code);
/**
* 查询是否存在未执行抽奖领取活动单【user_take_activity 存在 state = 0,领取了但抽奖过程失败的,可以直接返回领取结果继续抽奖】
*
......
......@@ -3,6 +3,7 @@ package cn.happy.lottery.domain.activity.service.partake;
import cn.happy.lottery.common.Result;
import cn.happy.lottery.domain.activity.model.req.PartakeReq;
import cn.happy.lottery.domain.activity.model.res.PartakeResult;
import cn.happy.lottery.domain.activity.model.vo.ActivityPartakeRecordVO;
import cn.happy.lottery.domain.activity.model.vo.DrawOrderVO;
import cn.happy.lottery.domain.activity.model.vo.InvoiceVO;
......@@ -46,4 +47,10 @@ public interface IActivityPartake {
*/
List<InvoiceVO> scanInvoiceMqState(int dbCount, int tbCount);
/**
* 更新活动库存
*
* @param activityPartakeRecordVO 活动领取记录
*/
void updateActivityStock(ActivityPartakeRecordVO activityPartakeRecordVO);
}
......@@ -3,10 +3,8 @@ package cn.happy.lottery.domain.activity.service.partake.impl;
import cn.happy.lottery.common.Constants;
import cn.happy.lottery.common.Result;
import cn.happy.lottery.domain.activity.model.req.PartakeReq;
import cn.happy.lottery.domain.activity.model.vo.ActivityBillVO;
import cn.happy.lottery.domain.activity.model.vo.DrawOrderVO;
import cn.happy.lottery.domain.activity.model.vo.InvoiceVO;
import cn.happy.lottery.domain.activity.model.vo.UserTakeActivityVO;
import cn.happy.lottery.domain.activity.model.res.StockResult;
import cn.happy.lottery.domain.activity.model.vo.*;
import cn.happy.lottery.domain.activity.repository.IUserTakeActivityRepository;
import cn.happy.lottery.domain.activity.service.partake.BaseActivityPartake;
import cn.happy.middleware.db.router.strategy.IDBRouterStrategy;
......@@ -40,6 +38,16 @@ public class ActivityPartakeImpl extends BaseActivityPartake {
private IDBRouterStrategy dbRouter;
@Override
protected StockResult subtractionActivityStockByRedis(String uId, Long activityId, Integer stockCount) {
return activityRepository.subtractionActivityStockByRedis(uId, activityId, stockCount);
}
@Override
protected void recoverActivityCacheStockByRedis(Long activityId, String tokenKey, String code) {
activityRepository.recoverActivityCacheStockByRedis(activityId, tokenKey, code);
}
@Override
protected UserTakeActivityVO queryNoConsumedTakeActivityOrder(Long activityId, String uId) {
return userTakeActivityRepository.queryNoConsumedTakeActivityOrder(activityId, uId);
......@@ -161,4 +169,9 @@ public class ActivityPartakeImpl extends BaseActivityPartake {
dbRouter.clear();
}
}
@Override
public void updateActivityStock(ActivityPartakeRecordVO activityPartakeRecordVO) {
userTakeActivityRepository.updateActivityStock(activityPartakeRecordVO);
}
}
......@@ -11,7 +11,7 @@ import java.util.List;
/**
* @author Happy
* @description:
* @description: 抽奖策略流程实现
* @date 2022/2/7
*/
@Service("drawExec")
......
package cn.happy.lottery.domain.support.redis;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @author: happy
* @date: 2022/4/6
* @description:
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
/**
* template 相关配置
*
* @param factory RedisConnectionFactory
* @return RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 配置连接工厂
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值(默认使用 JDK 的序列化方式)
Jackson2JsonRedisSerializer jacksonSerial = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get,set 以及修饰符范围,ANY 是都由包括 private 和 public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如 String, Integer 会出异常
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSerial.setObjectMapper(om);
// 值采用json序列化
template.setValueSerializer(jacksonSerial);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
// 设置 hash key 和 value 序列化模式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(jacksonSerial);
template.afterPropertiesSet();
return template;
}
/**
* 对 HASH 类型的数据操作
*
* @param redisTemplate RedisTemplate
* @return HashOperations
*/
@Bean
public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForHash();
}
/**
* 对redis字符串类型数据操作
*
* @param redisTemplate RedisTemplate
* @return ValueOperations
*/
@Bean
public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForValue();
}
/**
* 对链表类型的数据操作
*
* @param redisTemplate RedisTemplate
* @return ListOperations
*/
@Bean
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForList();
}
/**
* 对无序集合类型的数据操作
*
* @param redisTemplate RedisTemplate
* @return SetOperations
*/
@Bean
public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForSet();
}
/**
* 对有序集合类型的数据操作
*
* @param redisTemplate RedisTemplate
* @return ZSetOperations
*/
@Bean
public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForZSet();
}
}
......@@ -79,4 +79,11 @@ public interface IActivityDao {
*/
void updateActivity(Activity activity);
/**
* 更新用户领取活动后,活动库存
*
* @param activity 入参
*/
void updateActivityStock(Activity activity);
}
......@@ -4,10 +4,14 @@ import cn.happy.lottery.common.Constants;
import cn.happy.lottery.domain.activity.model.aggregates.ActivityInfoLimitPageRich;
import cn.happy.lottery.domain.activity.model.req.ActivityInfoLimitPageReq;
import cn.happy.lottery.domain.activity.model.req.PartakeReq;
import cn.happy.lottery.domain.activity.model.res.StockResult;
import cn.happy.lottery.domain.activity.model.vo.*;
import cn.happy.lottery.domain.activity.repository.IActivityRepository;
import cn.happy.lottery.infrastructure.dao.*;
import cn.happy.lottery.infrastructure.po.*;
import cn.happy.lottery.infrastructure.util.RedisUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Repository;
......@@ -24,6 +28,9 @@ import java.util.List;
@Repository
public class ActivityRepository implements IActivityRepository {
private Logger logger = LoggerFactory.getLogger(ActivityRepository.class);
@Resource
private IActivityDao activityDao;
......@@ -39,11 +46,17 @@ public class ActivityRepository implements IActivityRepository {
@Resource
private IUserTakeActivityCountDao userTakeActivityCountDao;
@Resource
private RedisUtil redisUtil;
@Override
public void addActivity(ActivityVO activity) {
Activity req = new Activity();
BeanUtils.copyProperties(activity, req);
activityDao.insert(req);
// 设置活动库存 KEY
redisUtil.set(Constants.RedisKey.KEY_LOTTERY_ACTIVITY_STOCK_COUNT(activity.getActivityId()), 0);
}
@Override
......@@ -96,6 +109,9 @@ public class ActivityRepository implements IActivityRepository {
// 查询活动信息
Activity activity = activityDao.queryActivityById(req.getActivityId());
// 从缓存中获取库存
Object usedStockCountObj = redisUtil.get(Constants.RedisKey.KEY_LOTTERY_ACTIVITY_STOCK_COUNT(req.getActivityId()));
// 查询领取次数
UserTakeActivityCount userTakeActivityCountReq = new UserTakeActivityCount();
userTakeActivityCountReq.setuId(req.getuId());
......@@ -110,7 +126,8 @@ public class ActivityRepository implements IActivityRepository {
activityBillVO.setBeginDateTime(activity.getBeginDateTime());
activityBillVO.setEndDateTime(activity.getEndDateTime());
activityBillVO.setTakeCount(activity.getTakeCount());
activityBillVO.setStockSurplusCount(activity.getStockSurplusCount());
activityBillVO.setStockCount(activity.getStockCount());
activityBillVO.setStockSurplusCount(null == usedStockCountObj ? activity.getStockSurplusCount() : Integer.parseInt(String.valueOf(usedStockCountObj)));
activityBillVO.setStrategyId(activity.getStrategyId());
activityBillVO.setState(activity.getState());
activityBillVO.setUserTakeLeftCount(null == userTakeActivityCount ? null : userTakeActivityCount.getLeftCount());
......@@ -192,5 +209,60 @@ public class ActivityRepository implements IActivityRepository {
BeanUtils.copyProperties(activityVO, activity);
activityDao.updateActivity(activity);
}
@Override
public StockResult subtractionActivityStockByRedis(String uId, Long activityId, Integer stockCount) {
// 1. 获取抽奖活动库存 Key
String stockKey = Constants.RedisKey.KEY_LOTTERY_ACTIVITY_STOCK_COUNT(activityId);
// 2. 扣减库存,目前占用库存数【以增代减】
Integer stockUsedCount = (int) redisUtil.incr(stockKey, 1);
// 3. 超出库存判断,进行恢复原始库存
if (stockUsedCount > stockCount) {
redisUtil.decr(stockKey, 1);
return new StockResult(Constants.ResponseCode.OUT_OF_STOCK.getCode(), Constants.ResponseCode.OUT_OF_STOCK.getInfo());
}
// 4. 以活动库存占用编号,生成对应加锁Key,细化锁粒度
String stockTokenKey = Constants.RedisKey.KEY_LOTTERY_ACTIVITY_STOCK_COUNT_TOKEN(activityId, stockUsedCount);
// 5. 使用 Redis.setNx 加一个分布式锁
boolean lockToken = redisUtil.setNx(stockTokenKey, 350L);
if (!lockToken) {
logger.info("抽奖活动: {} 用户秒杀: {} 扣减库存,分布式锁失败: {}", activityId, uId, stockTokenKey);
return new StockResult(Constants.ResponseCode.ERR_TOKEN.getCode(), Constants.ResponseCode.ERR_TOKEN.getInfo());
}
return new StockResult(Constants.ResponseCode.SUCCESS.getCode(), Constants.ResponseCode.SUCCESS.getInfo(), stockTokenKey, stockCount - stockUsedCount);
}
@Override
public void recoverActivityCacheStockByRedis(Long activityId, String tokenKey, String code) {
// 删除分布式锁 Key
redisUtil.del(tokenKey);
}
}
package cn.happy.lottery.infrastructure.repository;
import cn.happy.lottery.common.Constants;
import cn.happy.lottery.domain.activity.model.vo.ActivityPartakeRecordVO;
import cn.happy.lottery.domain.activity.model.vo.DrawOrderVO;
import cn.happy.lottery.domain.activity.model.vo.InvoiceVO;
import cn.happy.lottery.domain.activity.model.vo.UserTakeActivityVO;
import cn.happy.lottery.domain.activity.repository.IUserTakeActivityRepository;
import cn.happy.lottery.infrastructure.dao.IActivityDao;
import cn.happy.lottery.infrastructure.dao.IUserStrategyExportDao;
import cn.happy.lottery.infrastructure.dao.IUserTakeActivityCountDao;
import cn.happy.lottery.infrastructure.dao.IUserTakeActivityDao;
import cn.happy.lottery.infrastructure.po.Activity;
import cn.happy.lottery.infrastructure.po.UserStrategyExport;
import cn.happy.lottery.infrastructure.po.UserTakeActivity;
import cn.happy.lottery.infrastructure.po.UserTakeActivityCount;
......@@ -27,6 +30,9 @@ import java.util.List;
@Repository
public class UserTakeActivityRepository implements IUserTakeActivityRepository {
@Resource
private IActivityDao activityDao;
@Resource
private IUserTakeActivityCountDao userTakeActivityCountDao;
......@@ -159,4 +165,12 @@ public class UserTakeActivityRepository implements IUserTakeActivityRepository {
}
return invoiceVOList;
}
@Override
public void updateActivityStock(ActivityPartakeRecordVO activityPartakeRecordVO) {
Activity activity = new Activity();
activity.setActivityId(activityPartakeRecordVO.getActivityId());
activity.setStockSurplusCount(activityPartakeRecordVO.getStockSurplusCount());
activityDao.updateActivityStock(activity);
}
}
package cn.happy.lottery.infrastructure.util;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @author: happy
* @date: 2022/4/6
* @description: Redis 工具类
*/
@Component
public class RedisUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;
public RedisUtil(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
}
}
}
//============================String=============================
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 分布式锁
* @param key 锁住的key
* @param lockExpireMils 锁住的时长。如果超时未解锁,视为加锁线程死亡,其他线程可夺取锁
* @return
*/
public boolean setNx(String key, Long lockExpireMils) {
return (boolean) redisTemplate.execute((RedisCallback) connection -> {
//获取锁
return connection.setNX(key.getBytes(), String.valueOf(System.currentTimeMillis() + lockExpireMils + 1).getBytes());
});
}
/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
//================================Map=================================
/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
//============================set=============================
/**
* 根据key获取Set中的所有值
*
* @param key 键
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) {
expire(key, time);
}
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
//===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 模糊查询获取key值
*
* @param pattern
* @return
*/
public Set keys(String pattern) {
return redisTemplate.keys(pattern);
}
/**
* 使用Redis的消息队列
*
* @param channel
* @param message 消息内容
*/
public void convertAndSend(String channel, Object message) {
redisTemplate.convertAndSend(channel, message);
}
//=========BoundListOperations 用法 start============
/**
* 将数据添加到Redis的list中(从右边添加)
*
* @param listKey
* @param timeout 有效时间
* @param unit 时间类型
* @param values 待添加的数据
*/
public void addToListRight(String listKey, long timeout, TimeUnit unit, Object... values) {
//绑定操作
BoundListOperations<String, Object> boundValueOperations = redisTemplate.boundListOps(listKey);
//插入数据
boundValueOperations.rightPushAll(values);
//设置过期时间
boundValueOperations.expire(timeout, unit);
}
/**
* 根据起始结束序号遍历Redis中的list
*
* @param listKey
* @param start 起始序号
* @param end 结束序号
* @return
*/
public List<Object> rangeList(String listKey, long start, long end) {
//绑定操作
BoundListOperations<String, Object> boundValueOperations = redisTemplate.boundListOps(listKey);
//查询数据
return boundValueOperations.range(start, end);
}
/**
* 弹出右边的值 --- 并且移除这个值
*
* @param listKey
*/
public Object rightPop(String listKey) {
//绑定操作
BoundListOperations<String, Object> boundValueOperations = redisTemplate.boundListOps(listKey);
return boundValueOperations.rightPop();
}
//=========BoundListOperations 用法 End============
}
server:
port: 8089
# Redis 云服务器搭建后配置并重启 【否则会 Unable to connect to 39.96.*.*:6379】
# 防火墙放行:firewall-cmd --zone=public --add-port=6379/tcp --permanent
# 防火墙重启:firewall-cmd --reload
# redis.conf 注释掉 bind 127.0.0.1
# redis.conf protected-mode yes 改为 protected-mode no
redis:
database: 0
host: 127.0.0.1 # Redis服务器地址,修改为你的地址
port: 6379 # Redis服务器连接端口
password: # Redis服务器连接密码(默认为空)
timeout: 3000 # Redis服务器链接超时配置
# xxl-job
# 官网:https://github.com/xuxueli/xxl-job/
# 地址:http://localhost:7397/xxl-job-admin 【需要先启动 xxl-job】
......
......@@ -142,4 +142,9 @@
LIMIT #{pageBegin}, #{pageEnd}
</select>
<update id="updateActivityStock" parameterType="cn.happy.lottery.infrastructure.po.Activity">
UPDATE activity SET stock_surplus_count = #{stockSurplusCount}
WHERE activity_id = #{activityId} AND stock_surplus_count > #{stockSurplusCount}
</update>
</mapper>
......@@ -16,7 +16,7 @@ import javax.annotation.Resource;
/**
* @author Happy
* @description:
* @date 2022/2/28
* @date 2022/2/281
*/
@RunWith(SpringRunner.class)
@SpringBootTest
......@@ -37,7 +37,6 @@ public class ActivityProcessTest {
logger.info("请求入参:{}", JSON.toJSONString(req));
logger.info("测试结果:{}", JSON.toJSONString(drawProcessResult));
}
......
package cn.happy.lottery.test.dao;
package cn.happy.lottery.test.infrastructure;
import cn.happy.lottery.common.Constants;
import cn.happy.lottery.domain.activity.model.aggregates.ActivityConfigRich;
import cn.happy.lottery.domain.activity.model.req.ActivityConfigReq;
import cn.happy.lottery.domain.activity.model.vo.ActivityVO;
import cn.happy.lottery.domain.activity.model.vo.AwardVO;
import cn.happy.lottery.domain.activity.model.vo.StrategyDetailVO;
import cn.happy.lottery.domain.activity.model.vo.StrategyVO;
import cn.happy.lottery.domain.activity.service.deploy.IActivityDeploy;
import cn.happy.lottery.domain.activity.service.stateflow.IStateHandler;
import cn.happy.lottery.infrastructure.dao.IActivityDao;
import cn.happy.lottery.infrastructure.po.Activity;
import cn.happy.lottery.test.SpringRunnerTest;
import com.alibaba.fastjson.JSON;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
......@@ -22,10 +12,7 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @author Happy
......
package cn.happy.lottery.test.dao;
package cn.happy.lottery.test.infrastructure;
import cn.happy.lottery.common.Constants;
import cn.happy.lottery.domain.activity.model.vo.AwardVO;
......
package cn.happy.lottery.test.infrastructure;
import cn.happy.lottery.infrastructure.util.RedisUtil;
import com.alibaba.fastjson.JSON;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
/**
* @author: happy
* @date: 2022/4/6
* @description: Redis 使用测试
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisUtilTest {
private Logger logger = LoggerFactory.getLogger(RedisUtilTest.class);
@Resource
private RedisUtil redisUtil;
@Test
public void test_set() {
redisUtil.set("lottery_user_key", "happy");
}
@Test
public void test_get() {
Object user = redisUtil.get("lottery_user_key");
logger.info("测试结果:{}", JSON.toJSONString(user));
}
}
package cn.happy.lottery.test.dao;
package cn.happy.lottery.test.infrastructure;
import cn.happy.lottery.common.Constants;
import cn.happy.lottery.infrastructure.dao.IStrategyDao;
......
package cn.happy.lottery.test.dao;
package cn.happy.lottery.test.infrastructure;
import cn.happy.lottery.common.Constants;
import cn.happy.lottery.infrastructure.dao.IStrategyDetailDao;
......
package cn.happy.lottery.test.dao;
package cn.happy.lottery.test.infrastructure;
import cn.happy.lottery.common.Constants;
import cn.happy.lottery.domain.support.ids.IIdGenerator;
......
package cn.happy.lottery.test.dao;
package cn.happy.lottery.test.infrastructure;
import cn.happy.lottery.infrastructure.dao.IUserTakeActivityDao;
import cn.happy.lottery.infrastructure.po.UserTakeActivity;
......
......@@ -26,7 +26,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.5.RELEASE</version>
<version>2.6.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册