package com.alatus.mall.seckill.service.impl;
import com.alatus.common.to.mq.SeckillOrderTo;
import com.alatus.common.utils.R;
import com.alatus.common.vo.MemberRespVo;
import com.alatus.mall.seckill.constant.SecKillConstants;
import com.alatus.mall.seckill.feign.CouponFeignService;
import com.alatus.mall.seckill.feign.ProductFeignService;
import com.alatus.mall.seckill.interceptor.LoginUserInterceptor;
import com.alatus.mall.seckill.service.SecKillService;
import com.alatus.mall.seckill.to.SecKillSkuRedisTo;
import com.alatus.mall.seckill.vo.SeckillSessionEntityVo;
import com.alatus.mall.seckill.vo.SkuInfoVo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.apache.commons.lang.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Service
public class SecKillServiceImpl implements SecKillService {
@Autowired
private CouponFeignService couponFeignService;
@Autowired
private ProductFeignService productFeignService;
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private RedissonClient redissonClient;
@Autowired
private RabbitTemplate rabbitTemplate;
@Override
public void uploadSecKillSkuLatest3Days() {
// 扫描需要参与秒杀的活动
R daySession = couponFeignService.Latest3DaySession();
if(daySession.getCode() == 0){
List<SeckillSessionEntityVo> seckillSessionVos = daySession.getData(new TypeReference<List<SeckillSessionEntityVo>>() {});
// 缓存活动信息
saveSessionInfos(seckillSessionVos);
// 缓存活动关联的商品信息
saveSkuInfos(seckillSessionVos);
}
}
// 返回当前事件可以参与的秒杀商品信息
@Override
public List<SecKillSkuRedisTo> getCurrentSeckillSkus() {
long time = new Date().getTime();
Set<String> keys = redisTemplate.keys(SecKillConstants.SESSION_CACHE_PREFIX + "*");
List<Object> currentSeckillSkus = new ArrayList<>();
if (keys != null) {
for (String key : keys) {
String replace = key.replace(SecKillConstants.SESSION_CACHE_PREFIX, "");
String[] tempString = replace.split(":");
String timeString = tempString[1];
String[] timeSplit = timeString.split("-");
long start = Long.parseLong(timeSplit[0]);
long end = Long.parseLong(timeSplit[1]);
if(time >= start && time <= end){
List<String> range = redisTemplate.opsForList().range(key, -100, 100);
BoundHashOperations<String, String, Object> operations = redisTemplate.boundHashOps(SecKillConstants.SKU_CACHE_PREFIX);
List<Object> strings = null;
if (range != null) {
strings = operations.multiGet(range);
}
if(strings != null && !strings.isEmpty()){
currentSeckillSkus.addAll(strings);
}
}
}
}
if(!currentSeckillSkus.isEmpty()){
// 遍历获取到的JSON集合并转换为对象,并完整添加进去方便展示出来
return currentSeckillSkus.stream().map(item -> {
SecKillSkuRedisTo secKillSkuRedisTo = JSON.parseObject(item.toString(), SecKillSkuRedisTo.class);
// 屏蔽掉随机码
secKillSkuRedisTo.setRandomCode(null);
return secKillSkuRedisTo;
}).collect(Collectors.toList());
}
else{
return null;
}
}
@Override
public SecKillSkuRedisTo getSkuSeckillInfo(Long skuId) {
// 找到所有需要参与秒杀的SKU的Key信息
BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(SecKillConstants.SKU_CACHE_PREFIX);
Set<String> keys = operations.keys();
if (keys != null && !keys.isEmpty()) {
String regx = "\\d_" +skuId;
for (String key : keys) {
if(Pattern.matches(regx,key)){
String json = operations.get(key);
SecKillSkuRedisTo secKillSkuRedisTo = JSON.parseObject(json, SecKillSkuRedisTo.class);
if(secKillSkuRedisTo.getEndTime() > new Date().getTime()){
if(secKillSkuRedisTo.getStartTime() > new Date().getTime()){
secKillSkuRedisTo.setRandomCode(null);
}
return secKillSkuRedisTo;
}
}
}
}
return null;
}
@Override
public String kill(String killId, String key, Integer num) {
MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
// 获取当前秒杀商品的详细信息
BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(SecKillConstants.SKU_CACHE_PREFIX);
String skuInfo = operations.get(killId);
// 没找到这么一个秒杀商品
if(!StringUtils.isEmpty(skuInfo)){
SecKillSkuRedisTo secKillSkuRedisTo = JSON.parseObject(skuInfo, SecKillSkuRedisTo.class);
Long startTime = secKillSkuRedisTo.getStartTime();
Long endTime = secKillSkuRedisTo.getEndTime();
long time = new Date().getTime();
// 在活动时间范围内
if(time >= startTime && time <= endTime){
// 拼接商品ID
String skuId = secKillSkuRedisTo.getPromotionSessionId()+"_"+secKillSkuRedisTo.getSkuId();
// 商品随机码
String randomCode = secKillSkuRedisTo.getRandomCode();
// 校验随机码和商品ID
if(key.equals(randomCode) && killId.equals(skuId)){
// 验证购物数量是否合理
if(secKillSkuRedisTo.getSeckillLimit().intValue() >= num){
String redisKey = memberRespVo.getId() + "_" + skuId;
// 验证这个人是否已经购买过,幂等性处理,如果秒杀成功,就去redis占位
// 而且这个占位必须让它自动过期,不然会一直占用redis的资源
long ttl = endTime - time;
Boolean check = redisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
// 如果这里可以占位成功说明,当前用户没有在该场次购买过该商品
if(check){
RSemaphore semaphore = redissonClient.getSemaphore(SecKillConstants.SKU_STOCK_SEMAPHORE + randomCode);
try {
boolean acquire = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
// 秒杀成功以后,我们就可以做一个快速下单,发送一个MQ消息
if(acquire){
String orderSn = IdWorker.getTimeId();
SeckillOrderTo secKillOrderTo = new SeckillOrderTo();
BeanUtils.copyProperties(secKillSkuRedisTo,secKillOrderTo);
secKillOrderTo.setOrderSn(orderSn);
secKillOrderTo.setMemberId(memberRespVo.getId());
secKillOrderTo.setNum(num);
rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",secKillOrderTo);
// TODO 发送MQ消息
return orderSn;
}
} catch (InterruptedException e) {
return null;
}
}
}
}
}
}
return null;
}
private void saveSessionInfos(List<SeckillSessionEntityVo> seckillSessionVos) {
seckillSessionVos.stream().forEach(session -> {
//获取秒杀活动开始和结束时间
long start = session.getStartTime().getTime();
long end = session.getEndTime().getTime();
String key = SecKillConstants.SESSION_CACHE_PREFIX+session.getName()+":"+start+"-"+end;
// 保存活动的信息
if (!redisTemplate.hasKey(key)) {
List<String> ids = session.getRelationSkus().stream().map(item->{
return item.getPromotionSessionId()+"_"+item.getSkuId().toString();
}).collect(Collectors.toList());
redisTemplate.opsForList().leftPushAll(key,ids);
}
});
}
private void saveSkuInfos(List<SeckillSessionEntityVo> seckillSessionVos){
seckillSessionVos.forEach(session -> {
// 准备哈希操作
BoundHashOperations ops = redisTemplate.boundHashOps(SecKillConstants.SKU_CACHE_PREFIX);
session.getRelationSkus().forEach(relationSku -> {
// 生成随机码
String token = UUID.randomUUID().toString().replace("-", "");
// 拼接Key保存商品
String key = relationSku.getPromotionSessionId().toString()+"_"+relationSku.getSkuId().toString();
if(!ops.hasKey(key)){
// 缓存商品
SecKillSkuRedisTo secKillSkuRedisTo = new SecKillSkuRedisTo();
// sku的基本信息
R info = productFeignService.getSkuInfo(relationSku.getSkuId());
if(info.getCode() == 0){
SkuInfoVo skuInfoVo = info.get("skuInfo",new TypeReference<SkuInfoVo>(){});
secKillSkuRedisTo.setSkuInfo(skuInfoVo);
}
// sku的秒杀信息
BeanUtils.copyProperties(relationSku,secKillSkuRedisTo);
// 设置上秒杀数量啊!!
secKillSkuRedisTo.setSeckillCount(relationSku.getSeckillCount());
// 设置秒杀的时间
secKillSkuRedisTo.setStartTime(session.getStartTime().getTime());
secKillSkuRedisTo.setEndTime(session.getEndTime().getTime());
secKillSkuRedisTo.setRandomCode(token);
// 保存商品
String skuJson = JSON.toJSONString(secKillSkuRedisTo);
ops.put(key,skuJson);
// 如果当前这个场次的商品的库存信息已经上架,就不需要再次上架了
// 设置库存为分布式秒杀的信号量,限流
RSemaphore semaphore = redissonClient.getSemaphore(SecKillConstants.SKU_STOCK_SEMAPHORE + token);
// 商品可以秒杀的数量作为信号量
semaphore.trySetPermits(relationSku.getSeckillCount().intValue());
}
});
});
}
}
package com.alatus.mall.seckill.service.impl;
import com.alatus.common.to.mq.SeckillOrderTo;
import com.alatus.common.utils.R;
import com.alatus.common.vo.MemberRespVo;
import com.alatus.mall.seckill.constant.SecKillConstants;
import com.alatus.mall.seckill.feign.CouponFeignService;
import com.alatus.mall.seckill.feign.ProductFeignService;
import com.alatus.mall.seckill.interceptor.LoginUserInterceptor;
import com.alatus.mall.seckill.service.SecKillService;
import com.alatus.mall.seckill.to.SecKillSkuRedisTo;
import com.alatus.mall.seckill.vo.SeckillSessionEntityVo;
import com.alatus.mall.seckill.vo.SkuInfoVo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.apache.commons.lang.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Service
public class SecKillServiceImpl implements SecKillService {
@Autowired
private CouponFeignService couponFeignService;
@Autowired
private ProductFeignService productFeignService;
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private RedissonClient redissonClient;
@Autowired
private RabbitTemplate rabbitTemplate;
@Override
public void uploadSecKillSkuLatest3Days() {
// 扫描需要参与秒杀的活动
R daySession = couponFeignService.Latest3DaySession();
if(daySession.getCode() == 0){
List<SeckillSessionEntityVo> seckillSessionVos = daySession.getData(new TypeReference<List<SeckillSessionEntityVo>>() {});
// 缓存活动信息
saveSessionInfos(seckillSessionVos);
// 缓存活动关联的商品信息
saveSkuInfos(seckillSessionVos);
}
}
// 返回当前事件可以参与的秒杀商品信息
@Override
public List<SecKillSkuRedisTo> getCurrentSeckillSkus() {
long time = new Date().getTime();
Set<String> keys = redisTemplate.keys(SecKillConstants.SESSION_CACHE_PREFIX + "*");
List<Object> currentSeckillSkus = new ArrayList<>();
if (keys != null) {
for (String key : keys) {
String replace = key.replace(SecKillConstants.SESSION_CACHE_PREFIX, "");
String[] tempString = replace.split(":");
String timeString = tempString[1];
String[] timeSplit = timeString.split("-");
long start = Long.parseLong(timeSplit[0]);
long end = Long.parseLong(timeSplit[1]);
if(time >= start && time <= end){
List<String> range = redisTemplate.opsForList().range(key, -100, 100);
BoundHashOperations<String, String, Object> operations = redisTemplate.boundHashOps(SecKillConstants.SKU_CACHE_PREFIX);
List<Object> strings = null;
if (range != null) {
strings = operations.multiGet(range);
}
if(strings != null && !strings.isEmpty()){
currentSeckillSkus.addAll(strings);
}
}
}
}
if(!currentSeckillSkus.isEmpty()){
// 遍历获取到的JSON集合并转换为对象,并完整添加进去方便展示出来
return currentSeckillSkus.stream().map(item -> {
SecKillSkuRedisTo secKillSkuRedisTo = JSON.parseObject(item.toString(), SecKillSkuRedisTo.class);
// 屏蔽掉随机码
secKillSkuRedisTo.setRandomCode(null);
return secKillSkuRedisTo;
}).collect(Collectors.toList());
}
else{
return null;
}
}
@Override
public SecKillSkuRedisTo getSkuSeckillInfo(Long skuId) {
// 找到所有需要参与秒杀的SKU的Key信息
BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(SecKillConstants.SKU_CACHE_PREFIX);
Set<String> keys = operations.keys();
if (keys != null && !keys.isEmpty()) {
String regx = "\\d_" +skuId;
for (String key : keys) {
if(Pattern.matches(regx,key)){
String json = operations.get(key);
SecKillSkuRedisTo secKillSkuRedisTo = JSON.parseObject(json, SecKillSkuRedisTo.class);
if(secKillSkuRedisTo.getEndTime() > new Date().getTime()){
if(secKillSkuRedisTo.getStartTime() > new Date().getTime()){
secKillSkuRedisTo.setRandomCode(null);
}
return secKillSkuRedisTo;
}
}
}
}
return null;
}
@Override
public String kill(String killId, String key, Integer num) {
MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
// 获取当前秒杀商品的详细信息
BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(SecKillConstants.SKU_CACHE_PREFIX);
String skuInfo = operations.get(killId);
// 没找到这么一个秒杀商品
if(!StringUtils.isEmpty(skuInfo)){
SecKillSkuRedisTo secKillSkuRedisTo = JSON.parseObject(skuInfo, SecKillSkuRedisTo.class);
Long startTime = secKillSkuRedisTo.getStartTime();
Long endTime = secKillSkuRedisTo.getEndTime();
long time = new Date().getTime();
// 在活动时间范围内
if(time >= startTime && time <= endTime){
// 拼接商品ID
String skuId = secKillSkuRedisTo.getPromotionSessionId()+"_"+secKillSkuRedisTo.getSkuId();
// 商品随机码
String randomCode = secKillSkuRedisTo.getRandomCode();
// 校验随机码和商品ID
if(key.equals(randomCode) && killId.equals(skuId)){
// 验证购物数量是否合理
if(secKillSkuRedisTo.getSeckillLimit().intValue() >= num){
String redisKey = memberRespVo.getId() + "_" + skuId;
// 验证这个人是否已经购买过,幂等性处理,如果秒杀成功,就去redis占位
// 而且这个占位必须让它自动过期,不然会一直占用redis的资源
long ttl = endTime - time;
Boolean check = redisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
// 如果这里可以占位成功说明,当前用户没有在该场次购买过该商品
if(check){
RSemaphore semaphore = redissonClient.getSemaphore(SecKillConstants.SKU_STOCK_SEMAPHORE + randomCode);
try {
boolean acquire = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
// 秒杀成功以后,我们就可以做一个快速下单,发送一个MQ消息
if(acquire){
String orderSn = IdWorker.getTimeId();
SeckillOrderTo secKillOrderTo = new SeckillOrderTo();
BeanUtils.copyProperties(secKillSkuRedisTo,secKillOrderTo);
secKillOrderTo.setOrderSn(orderSn);
secKillOrderTo.setMemberId(memberRespVo.getId());
secKillOrderTo.setNum(num);
rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",secKillOrderTo);
// TODO 发送MQ消息
return orderSn;
}
} catch (InterruptedException e) {
return null;
}
}
}
}
}
}
return null;
}
private void saveSessionInfos(List<SeckillSessionEntityVo> seckillSessionVos) {
seckillSessionVos.stream().forEach(session -> {
//获取秒杀活动开始和结束时间
long start = session.getStartTime().getTime();
long end = session.getEndTime().getTime();
String key = SecKillConstants.SESSION_CACHE_PREFIX+session.getName()+":"+start+"-"+end;
// 保存活动的信息
if (!redisTemplate.hasKey(key)) {
List<String> ids = session.getRelationSkus().stream().map(item->{
return item.getPromotionSessionId()+"_"+item.getSkuId().toString();
}).collect(Collectors.toList());
redisTemplate.opsForList().leftPushAll(key,ids);
}
});
}
private void saveSkuInfos(List<SeckillSessionEntityVo> seckillSessionVos){
seckillSessionVos.forEach(session -> {
// 准备哈希操作
BoundHashOperations ops = redisTemplate.boundHashOps(SecKillConstants.SKU_CACHE_PREFIX);
session.getRelationSkus().forEach(relationSku -> {
// 生成随机码
String token = UUID.randomUUID().toString().replace("-", "");
// 拼接Key保存商品
String key = relationSku.getPromotionSessionId().toString()+"_"+relationSku.getSkuId().toString();
if(!ops.hasKey(key)){
// 缓存商品
SecKillSkuRedisTo secKillSkuRedisTo = new SecKillSkuRedisTo();
// sku的基本信息
R info = productFeignService.getSkuInfo(relationSku.getSkuId());
if(info.getCode() == 0){
SkuInfoVo skuInfoVo = info.get("skuInfo",new TypeReference<SkuInfoVo>(){});
secKillSkuRedisTo.setSkuInfo(skuInfoVo);
}
// sku的秒杀信息
BeanUtils.copyProperties(relationSku,secKillSkuRedisTo);
// 设置上秒杀数量啊!!
secKillSkuRedisTo.setSeckillCount(relationSku.getSeckillCount());
// 设置秒杀的时间
secKillSkuRedisTo.setStartTime(session.getStartTime().getTime());
secKillSkuRedisTo.setEndTime(session.getEndTime().getTime());
secKillSkuRedisTo.setRandomCode(token);
// 保存商品
String skuJson = JSON.toJSONString(secKillSkuRedisTo);
ops.put(key,skuJson);
// 如果当前这个场次的商品的库存信息已经上架,就不需要再次上架了
// 设置库存为分布式秒杀的信号量,限流
RSemaphore semaphore = redissonClient.getSemaphore(SecKillConstants.SKU_STOCK_SEMAPHORE + token);
// 商品可以秒杀的数量作为信号量
semaphore.trySetPermits(relationSku.getSeckillCount().intValue());
}
});
});
}
}
package com.alatus.mall.member.config;
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.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
@Configuration
public class SessionConfig {
@Bean
public CookieSerializer cookieSerializer(){
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setDomainName("alatusmall.com");
cookieSerializer.setCookieName("MALLSESSION");
return cookieSerializer;
}
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer(){
return new GenericJackson2JsonRedisSerializer();
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setDefaultSerializer(springSessionDefaultRedisSerializer());
return template;
}
}
package com.alatus.mall.member.config;
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.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
@Configuration
public class SessionConfig {
@Bean
public CookieSerializer cookieSerializer(){
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setDomainName("alatusmall.com");
cookieSerializer.setCookieName("MALLSESSION");
return cookieSerializer;
}
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer(){
return new GenericJackson2JsonRedisSerializer();
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setDefaultSerializer(springSessionDefaultRedisSerializer());
return template;
}
}
package com.alatus.mall.seckill.scheduled;
import com.alatus.mall.seckill.service.SecKillService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* 秒杀商品的定时上架
* 每天晚上凌晨4点,上架最新三天秒杀商品
* 重复上架无需处理
*/
@Service
@Slf4j
public class SecKillSkuScheduled {
@Autowired
private SecKillService secKillService;
@Autowired
private RedissonClient redissonClient;
private final String upload_lock = "seckill:upload:lock";
@Scheduled(cron = "0 0 4 * * ?")
// TODO 幂等性处理
// @Scheduled(cron = "*/3 * * * * ?")
public void uploadSecKillSkuLatest3Days() {
log.info("上架秒杀的商品信息...");
RLock rLock = redissonClient.getLock(upload_lock);
rLock.lock(30, TimeUnit.SECONDS);
try{
secKillService.uploadSecKillSkuLatest3Days();
}finally {
rLock.unlock();
}
}
}
package com.alatus.mall.seckill.scheduled;
import com.alatus.mall.seckill.service.SecKillService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* 秒杀商品的定时上架
* 每天晚上凌晨4点,上架最新三天秒杀商品
* 重复上架无需处理
*/
@Service
@Slf4j
public class SecKillSkuScheduled {
@Autowired
private SecKillService secKillService;
@Autowired
private RedissonClient redissonClient;
private final String upload_lock = "seckill:upload:lock";
@Scheduled(cron = "0 0 4 * * ?")
// TODO 幂等性处理
// @Scheduled(cron = "*/3 * * * * ?")
public void uploadSecKillSkuLatest3Days() {
log.info("上架秒杀的商品信息...");
RLock rLock = redissonClient.getLock(upload_lock);
rLock.lock(30, TimeUnit.SECONDS);
try{
secKillService.uploadSecKillSkuLatest3Days();
}finally {
rLock.unlock();
}
}
}
package com.alatus.mall.order.config;
import com.alatus.common.constant.RabbitConstant;
import org.springframework.amqp.core.*;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RabbitConfig {
@Autowired
private RabbitMQConfigProperties rabbitMQConfigProperties;
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
// 容器中的Binding,Queue,Exchange都会自动创建,前提是rabbitmq没有
@Bean
public Queue orderDelayQueue(){
Map<String,Object> arguments = new HashMap<>();
arguments.put(RabbitConstant.X_DEAD_LETTER_EXCHANGE,rabbitMQConfigProperties.getExchange());
arguments.put(RabbitConstant.X_DEAD_LETTER_ROUTING_KEY,rabbitMQConfigProperties.getReleaseKey());
arguments.put(RabbitConstant.X_MESSAGE_TTL,rabbitMQConfigProperties.getMessageTTL());
return new Queue(rabbitMQConfigProperties.getDelayQueue(), true, false, false,arguments);
}
@Bean
public Queue orderReleaseQueue(){
return new Queue(rabbitMQConfigProperties.getReleaseQueue(), true, false, false);
}
@Bean
public Queue orderSeckillQueue(){
return new Queue(rabbitMQConfigProperties.getSeckillQueue(), true, false, false);
}
@Bean
public Exchange orderEventExchange(){
return new TopicExchange(rabbitMQConfigProperties.getExchange(),true,false);
}
@Bean
public Binding orderSeckill(){
return new Binding(rabbitMQConfigProperties.getSeckillQueue(), Binding.DestinationType.QUEUE,
rabbitMQConfigProperties.getExchange(), rabbitMQConfigProperties.getSeckillKey(),null);
}
@Bean
public Binding orderCreate(){
return new Binding(rabbitMQConfigProperties.getDelayQueue(), Binding.DestinationType.QUEUE,
rabbitMQConfigProperties.getExchange(), rabbitMQConfigProperties.getCreateKey(),null);
}
@Bean
public Binding orderRelease(){
return new Binding(rabbitMQConfigProperties.getReleaseQueue(), Binding.DestinationType.QUEUE,
rabbitMQConfigProperties.getExchange(),rabbitMQConfigProperties.getReleaseKey(),null);
}
// 订单和库存绑定
@Bean
public Binding orderReleaseOther(){
return new Binding(rabbitMQConfigProperties.getStockReleaseQueue(),Binding.DestinationType.QUEUE,
rabbitMQConfigProperties.getExchange(),rabbitMQConfigProperties.getStockReleaseKey(),null);
}
}
package com.alatus.mall.order.config;
import com.alatus.common.constant.RabbitConstant;
import org.springframework.amqp.core.*;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RabbitConfig {
@Autowired
private RabbitMQConfigProperties rabbitMQConfigProperties;
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
// 容器中的Binding,Queue,Exchange都会自动创建,前提是rabbitmq没有
@Bean
public Queue orderDelayQueue(){
Map<String,Object> arguments = new HashMap<>();
arguments.put(RabbitConstant.X_DEAD_LETTER_EXCHANGE,rabbitMQConfigProperties.getExchange());
arguments.put(RabbitConstant.X_DEAD_LETTER_ROUTING_KEY,rabbitMQConfigProperties.getReleaseKey());
arguments.put(RabbitConstant.X_MESSAGE_TTL,rabbitMQConfigProperties.getMessageTTL());
return new Queue(rabbitMQConfigProperties.getDelayQueue(), true, false, false,arguments);
}
@Bean
public Queue orderReleaseQueue(){
return new Queue(rabbitMQConfigProperties.getReleaseQueue(), true, false, false);
}
@Bean
public Queue orderSeckillQueue(){
return new Queue(rabbitMQConfigProperties.getSeckillQueue(), true, false, false);
}
@Bean
public Exchange orderEventExchange(){
return new TopicExchange(rabbitMQConfigProperties.getExchange(),true,false);
}
@Bean
public Binding orderSeckill(){
return new Binding(rabbitMQConfigProperties.getSeckillQueue(), Binding.DestinationType.QUEUE,
rabbitMQConfigProperties.getExchange(), rabbitMQConfigProperties.getSeckillKey(),null);
}
@Bean
public Binding orderCreate(){
return new Binding(rabbitMQConfigProperties.getDelayQueue(), Binding.DestinationType.QUEUE,
rabbitMQConfigProperties.getExchange(), rabbitMQConfigProperties.getCreateKey(),null);
}
@Bean
public Binding orderRelease(){
return new Binding(rabbitMQConfigProperties.getReleaseQueue(), Binding.DestinationType.QUEUE,
rabbitMQConfigProperties.getExchange(),rabbitMQConfigProperties.getReleaseKey(),null);
}
// 订单和库存绑定
@Bean
public Binding orderReleaseOther(){
return new Binding(rabbitMQConfigProperties.getStockReleaseQueue(),Binding.DestinationType.QUEUE,
rabbitMQConfigProperties.getExchange(),rabbitMQConfigProperties.getStockReleaseKey(),null);
}
}
/**
* Copyright (c) 2016-2019 人人开源 All rights reserved.
*
* https://www.renren.io
*
* 版权所有,侵权必究!
*/
package io.renren.config;
import org.springframework.beans.factory.annotation.Autowired;
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis配置
*
* @author Mark sunlightcs@gmail.com
*/
@Configuration
public class RedisConfig {
@Autowired
private RedisConnectionFactory factory;
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 设置 key 和 value 的序列化器为 JSON
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// 使用 GenericJackson2JsonRedisSerializer 序列化器来将数据以 JSON 格式存储
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
@Bean
public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForHash();
}
@Bean
public ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) {
return redisTemplate.opsForValue();
}
@Bean
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForList();
}
@Bean
public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForSet();
}
@Bean
public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForZSet();
}
}
/**
* Copyright (c) 2016-2019 人人开源 All rights reserved.
*
* https://www.renren.io
*
* 版权所有,侵权必究!
*/
package io.renren.config;
import org.springframework.beans.factory.annotation.Autowired;
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis配置
*
* @author Mark sunlightcs@gmail.com
*/
@Configuration
public class RedisConfig {
@Autowired
private RedisConnectionFactory factory;
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 设置 key 和 value 的序列化器为 JSON
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// 使用 GenericJackson2JsonRedisSerializer 序列化器来将数据以 JSON 格式存储
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
@Bean
public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForHash();
}
@Bean
public ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) {
return redisTemplate.opsForValue();
}
@Bean
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForList();
}
@Bean
public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForSet();
}
@Bean
public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForZSet();
}
}
package com.alatus.mall.order.listener;
import com.alatus.common.to.mq.SeckillOrderTo;
import com.alatus.mall.order.service.OrderService;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
@Slf4j
@RabbitListener(queues = "order.seckill.order.queue")
public class OrderSeckillListener {
@Autowired
private OrderService orderService;
@RabbitHandler
public void listener(SeckillOrderTo seckillOrderTo, Channel channel, Message message) throws IOException {
try{
log.info("收到秒杀单的订单信息,准备创建订单,订单号:{}",seckillOrderTo.getOrderSn());
orderService.createSeckillOrder(seckillOrderTo);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
catch (Exception e){
channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
}
}
}
package com.alatus.mall.order.listener;
import com.alatus.common.to.mq.SeckillOrderTo;
import com.alatus.mall.order.service.OrderService;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
@Slf4j
@RabbitListener(queues = "order.seckill.order.queue")
public class OrderSeckillListener {
@Autowired
private OrderService orderService;
@RabbitHandler
public void listener(SeckillOrderTo seckillOrderTo, Channel channel, Message message) throws IOException {
try{
log.info("收到秒杀单的订单信息,准备创建订单,订单号:{}",seckillOrderTo.getOrderSn());
orderService.createSeckillOrder(seckillOrderTo);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
catch (Exception e){
channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
}
}
}
package com.alatus.mall.coupon.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.math.BigDecimal;
import java.io.Serializable;
import lombok.Data;
/**
* 秒杀活动商品关联
*
* @author alatus
* @date 2024-03-12 13:32:54
*/
@Data
@TableName("sms_seckill_sku_relation")
public class SeckillSkuRelationEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* id
*/
@TableId
private Long id;
/**
* 活动id
*/
private Long promotionId;
/**
* 活动场次id
*/
private Long promotionSessionId;
/**
* 商品id
*/
private Long skuId;
/**
* 秒杀价格
*/
private BigDecimal seckillPrice;
/**
* 秒杀总量
*/
private BigDecimal seckillCount;
/**
* 每人限购数量
*/
private BigDecimal seckillLimit;
/**
* 排序
*/
private Integer seckillSort;
}
package com.alatus.mall.coupon.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.math.BigDecimal;
import java.io.Serializable;
import lombok.Data;
/**
* 秒杀活动商品关联
*
* @author alatus
* @date 2024-03-12 13:32:54
*/
@Data
@TableName("sms_seckill_sku_relation")
public class SeckillSkuRelationEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* id
*/
@TableId
private Long id;
/**
* 活动id
*/
private Long promotionId;
/**
* 活动场次id
*/
private Long promotionSessionId;
/**
* 商品id
*/
private Long skuId;
/**
* 秒杀价格
*/
private BigDecimal seckillPrice;
/**
* 秒杀总量
*/
private BigDecimal seckillCount;
/**
* 每人限购数量
*/
private BigDecimal seckillLimit;
/**
* 排序
*/
private Integer seckillSort;
}
package com.alatus.mall.seckill.to;
import com.alatus.mall.seckill.vo.SkuInfoVo;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class SecKillSkuRedisTo {
/**
* 活动id
*/
private Long promotionId;
/**
* 活动场次id
*/
private Long promotionSessionId;
/**
* 商品id
*/
private Long skuId;
/**
* 秒杀价格
*/
private BigDecimal seckillPrice;
/**
* 秒杀总量
*/
private BigDecimal seckillCount;
/**
* 每人限购数量
*/
private BigDecimal seckillLimit;
/**
* 排序
*/
private Integer seckillSort;
/**
* 商品详细信息
*/
private SkuInfoVo skuInfo;
/**
* 秒杀开始时间
*/
private Long startTime;
/**
* 秒杀结束时间
*/
private Long endTime;
/**
* 秒杀随机码
*/
private String randomCode;
}
package com.alatus.mall.seckill.to;
import com.alatus.mall.seckill.vo.SkuInfoVo;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class SecKillSkuRedisTo {
/**
* 活动id
*/
private Long promotionId;
/**
* 活动场次id
*/
private Long promotionSessionId;
/**
* 商品id
*/
private Long skuId;
/**
* 秒杀价格
*/
private BigDecimal seckillPrice;
/**
* 秒杀总量
*/
private BigDecimal seckillCount;
/**
* 每人限购数量
*/
private BigDecimal seckillLimit;
/**
* 排序
*/
private Integer seckillSort;
/**
* 商品详细信息
*/
private SkuInfoVo skuInfo;
/**
* 秒杀开始时间
*/
private Long startTime;
/**
* 秒杀结束时间
*/
private Long endTime;
/**
* 秒杀随机码
*/
private String randomCode;
}