一个完整的秒杀系统

一个完整的秒杀系统

加入缓存

package com.ckq.seckill.config;

import com.ckq.entity.Specification;
import com.ckq.seckill.global.Constants;
import com.ckq.seckill.service.SeckillService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Component
public class InitProject implements ApplicationRunner {

    @Autowired
    private SeckillService seckillService;

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    public void run(ApplicationArguments args) throws Exception {
        Map<String,Object> map=new HashMap<String, Object>();
        List<Specification> goods=seckillService.getgoodsById(map);
        for (int i=0;goods.size()>i;i++){
            stringRedisTemplate.opsForValue().set(Constants.PRODUCT_STOCK_PREFIX+goods.get(i).getId(),goods.get(i).getStock()+"",2, TimeUnit.HOURS);

        }
        System.out.println("缓存已就绪");
    }
}

rabbitmq配置

package com.ckq.seckill.config;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class MyMqconfig {
    //@RabbitListener(queues = "order.release.order.queue")
    public void listener(Object o, Channel channel, Message message) throws IOException {
        System.out.println("过期的消息 ===>  " + new String(message.getBody()) + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
    /**
     * 死信队列
     */
    @Bean
    public Queue orderDelayQueue() {
        //死信队列的参数
        Map<String, Object> params = new HashMap<>();
        params.put("x-dead-letter-exchange", "order-event-exchange");//死信路由名称
        params.put("x-dead-letter-routing-key", "order.release.order");//死信路由的路由键
        params.put("x-message-ttl", 60000);//消息过期时间(单位:毫秒)
        return new Queue("order.delay.queue", true, false, false, params);
    }
    @Bean
    public Queue orderReleaseOrder() {
        return new Queue("order.release.order.queue", true, false, false);
    }
    @Bean
    public Exchange orderEventExchange() {
        return new TopicExchange("order-event-exchange", true, false);
    }
    @Bean
    public Binding orderCreateOrderBinding() {
        return new Binding("order.delay.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.create.order", null);
    }
    @Bean
    public Binding orderReleaseOrderBinding() {
        return new Binding("order.release.order.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.release.order", null);
    }
}

redis 重写序列化

package com.ckq.seckill.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;

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String,Object> redisTemplate=new RedisTemplate<String, Object>();
        //更换key的序列化方式,针对字符串类型
        redisTemplate.setKeySerializer(new GenericJackson2JsonRedisSerializer());
        //更换value的序列化方式,针对字符串类型
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        //更换key的序列化方式,针对hash类型
        redisTemplate.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());
        //更换value的序列化方式,针对hash类型
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }

}

zookeeper配置

package com.ckq.seckill.config;

import com.ckq.seckill.service.impl.ZookeeperWatcher;
import org.apache.zookeeper.ZooKeeper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ZookeeperConfig {

    // 服务端 zookeeper 地址
    private static final String serverZookeeperAddress = "localhost:2181";

    @Bean
    public ZooKeeper initZookeeper() throws Exception {
        // 创建观察者
        ZookeeperWatcher watcher = new ZookeeperWatcher();
        // 创建 Zookeeper 客户端
        ZooKeeper zooKeeper = new ZooKeeper(serverZookeeperAddress, 30000, watcher);
        // 将客户端注册给观察者
        watcher.setZooKeeper(zooKeeper);
        // 将配置好的 zookeeper 返回
        return zooKeeper;
    }


}

zookeeper 调用

package com.ckq.seckill.config;

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

import java.util.concurrent.CountDownLatch;

public class ZookeeperSync implements Watcher {

    private static CountDownLatch connectSemaphore = new CountDownLatch(1);
    private static ZooKeeper zooKeeperClient = null;
    private static Stat stat = new Stat();

    public static void main(String[] args) throws Exception {
        // 文件存放路径
        String path = "/username";
        // 连接 zookeeper 并注册一个默认监听器
        zooKeeperClient = new ZooKeeper("192.168.0.12:2181", 5000, new com.ckq.seckill.config.ZookeeperSync());
        // 等待连接 zookeeper 成功通知
        connectSemaphore.await();
        // 获取 path 目录节点下的配置数据,并注册默认的监听器
        System.out.println(new String(zooKeeperClient.getData(path, true, stat)));

        Thread.sleep(Integer.MAX_VALUE);
    }

    @Override
    public void process(WatchedEvent watchedEvent) {
        if (watchedEvent.getType() == Event.EventType.None && watchedEvent.getPath() == null){
            connectSemaphore.countDown();
        }else if (watchedEvent.getType() == Event.EventType.NodeDataChanged){
            try {
                System.out.println("新值为:" + new String(zooKeeperClient.getData(watchedEvent.getPath(), true, stat)));
            } catch (KeeperException | InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

主控制层入口



package com.ckq.seckill.controller;



import com.ckq.config.JsonUtil;
import com.ckq.entity.InOrder;
import com.ckq.entity.Specification;
import com.ckq.seckill.global.Constants;
import com.ckq.seckill.service.SeckillService;
import com.rabbitmq.client.Channel;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


import java.io.IOException;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @ClassName OrdersController
 * @Description TODO
 * @Author XYM
 * @Date 2021/5/13 10:34
 * @Version 1.0
 **/
@RestController
@RequestMapping("/seckill")
public class SeckillController {
    @Autowired
    private SeckillService seckillService;
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private ZooKeeper zooKeeper;


    private static final ConcurrentHashMap<Integer, Boolean> productSoldMap=new ConcurrentHashMap<>();
    @PostMapping("goods")
    public String getgoods(){
        Map<String,Object> map=new HashMap<String, Object>();
        List<Specification> goods=seckillService.getgoodsById(map);
        return JsonUtil.JsonSerializer(goods);
    }
    @PostMapping("seckillgoods")
    public String getsecgoods(Specification specification){
        Map<String,Object> map=new HashMap<String, Object>();
        map.put("id",specification.getId());
        List<Specification> goods=seckillService.getgoodsById(map);
        Specification seckikkgood=goods.get(0);
        seckikkgood.setStarttime(seckikkgood.getStarttime());
        System.out.println(seckikkgood);
        return JsonUtil.JsonSerializer(seckikkgood);
    }
    @PostMapping("goodsbyId")
    public String getgoodsById(Integer id,Integer sellerId,Integer deliveryAddId) throws KeeperException, InterruptedException {
        if(productSoldMap.get(id) != null){
            System.out.println("直接返回");
            return "卖完了";
        }
        //String lock="lock";
      //  RLock rLock = redisson.getLock(lock);
        InOrder inOrder;
        try {
            //rLock.lock();
            Long stock = stringRedisTemplate.opsForValue().decrement(Constants.PRODUCT_STOCK_PREFIX+id);
            System.out.println(stock);
            if(stock<0){
                productSoldMap.put(id,true);
                stringRedisTemplate.opsForValue().increment(Constants.PRODUCT_STOCK_PREFIX + id);
                String productPath ="/"+Constants.PRODUCT_STOCK_PREFIX+"/"+id;
                if (zooKeeper.exists(productPath, true) == null) {
                    zooKeeper.create(productPath, "true".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                }
                // 监听 zookeeper 售完节点
                zooKeeper.exists(productPath, true);
                return "商品已售空";
                //throw new RuntimeException("商品已售空");

            }
            inOrder=seckillService.order(id,sellerId,deliveryAddId);
            }catch (Exception e){
            // 数据库减库存失败回滚已售罄列表记录
            if (productSoldMap.get(id) != null) {
                productSoldMap.remove(id);
            }
            // 通过 zookeeper 回滚其他服务器的 JVM 缓存中的商品售完标记
            String path = "/"+Constants.PRODUCT_STOCK_PREFIX+"/" + id;
            if (zooKeeper.exists(path, true) != null){
                zooKeeper.setData(path, "false".getBytes(), -1);
            }
            stringRedisTemplate.opsForValue().increment( Constants.PRODUCT_STOCK_PREFIX+ id);
            // 回滚 Redis 中的库存
            return "回滚";
            }
        return JsonUtil.JsonSerializer(inOrder);
    }
    @RabbitListener(queues = "order.release.order.queue")
    public void listener(InOrder inOrder, Channel channel, Message message) throws IOException {
        System.out.println(inOrder.toString());
        InOrder s= (InOrder) redisTemplate.opsForValue().get(inOrder.getOrderId());
        if(s.getOrderStep()==2){
            System.out.println("已支付,订单生效");
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }
        else{
            System.out.println("订单超时"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            stringRedisTemplate.opsForValue().increment( Constants.PRODUCT_STOCK_PREFIX+inOrder.getSpid());
            inOrder.setOrderStep(1);
            inOrder.setPayType(0);
            inOrder.setMark("超时订单");
            seckillService.buygodos(inOrder);
            seckillService.justock(inOrder.getSpid());
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }

    }

    @PostMapping("buy")
    public String buygoods(InOrder inOrder){
        inOrder.setMark("订单已付款");
        redisTemplate.opsForValue().set(inOrder.getOrderId(),inOrder);
        Integer count=seckillService.buygodos(inOrder);
        return JsonUtil.JsonSerializer(count);
    }
    public static ConcurrentHashMap<Integer,Boolean> getProductSoldMap() {
        return productSoldMap;
    }

}

逻辑层接口

package com.ckq.seckill.service;

import com.ckq.entity.InOrder;
import com.ckq.entity.Inorderlist;
import com.ckq.entity.Order;
import com.ckq.entity.Specification;

import java.util.List;
import java.util.Map;

public interface SeckillService {

    List<Specification> getgoodsById(Map<String,Object> map);

    InOrder order(Integer id,Integer sellerId,Integer deliveryAddId);

    Integer buygodos(InOrder inOrder);

    Integer justock(Integer id);
}

逻辑层实现

package com.ckq.seckill.service.impl;

import com.baomidou.mybatisplus.extension.api.R;
import com.ckq.entity.*;
import com.ckq.seckill.config.SnowFlakeId;
import com.ckq.seckill.dao.InOrderDao;
import com.ckq.seckill.dao.InOrderGoodsDao;
import com.ckq.seckill.dao.SeckillDao;
import com.ckq.seckill.service.SeckillService;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName OrdersServiceImpl
 * @Description TODO
 * @Author XYM
 * @Date 2021/5/13 10:54
 * @Version 1.0
 **/
@Service
public class SeckillServiceImpl implements SeckillService {
    @Autowired
    private SeckillDao seckillDao;
    @Autowired
    private InOrderDao inOrderDao;
    @Autowired
    private InOrderGoodsDao inOrderGoodsDao;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Override
    public List<Specification> getgoodsById(Map<String,Object> map) {
        return seckillDao.getgoodsById(map);
    }
    @Transactional
    public InOrder order(Integer id,Integer sellerId,Integer deliveryAddId){
        Map<String,Object> map=new HashMap<String, Object>();
        map.put("id",id);
        List<Specification> list=seckillDao.getgoodsById(map);
        if (list.get(0).getStock() <= 0){
            throw new RuntimeException("The product has been sold out.");
        }
        //创建订单
        InOrder inOrder=new InOrder();
        InOrderGoodsList inOrderGoodsList=new InOrderGoodsList();
        inOrderGoodsList.setGoodsId(list.get(0).getGoodsId());
        inOrderGoodsList.setGoodsImg(list.get(0).getGoodsImgUrl());
        inOrderGoodsList.setSpId(id);
        inOrderGoodsList.setBuyNum(1);
        inOrderGoodsList.setSname(list.get(0).getSname());
        inOrderGoodsList.setPrice(list.get(0).getPrice());
        inOrderGoodsList.setOrderId(SnowFlakeId.getSnowId());
        inOrderGoodsList.setGoodsName(list.get(0).getSname());
        seckillDao.addInOrderGoodsList(inOrderGoodsList);
        inOrder.setOrderId(inOrderGoodsList.getOrderId());
        inOrder.setSellerId(sellerId);
        inOrder.setAmount(list.get(0).getPrice());
        inOrder.setOrderStep(1);
        inOrder.setOrderState(1);
        inOrder.setPayType(2);
        inOrder.setSpid(id);
        inOrder.setDeliveryAddId(deliveryAddId);
        seckillDao.addInOrderlist(inOrder);
        createOrder(inOrder);
        System.out.println("+缓存");
        redisTemplate.opsForValue().set(inOrder.getOrderId(),inOrder,30, TimeUnit.MINUTES);
        System.out.println("+缓存");
        //减库存
        Integer count=seckillDao.jstock(id);
        System.out.println("减库存");
        if (count <= 0){
            System.out.println("减库存错误");
            throw new RuntimeException("The product has been sold out.");
        }
        return inOrder;
    }
    public R createOrder(InOrder inOrder) {
        System.out.println("创建订单===> " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        // 给MQ发消息
        rabbitTemplate.convertAndSend("order-event-exchange", "order.create.order", inOrder);
        return R.ok(new Date());
    }

    @Override
    public Integer buygodos(InOrder inOrder) {
        return seckillDao.buygodos(inOrder);
    }

    public Integer justock(Integer id){
        return seckillDao.ujstock(id);
    }
}

常量

package com.ckq.seckill.global;

public class Constants {

    public static final String PRODUCT_STOCK_PREFIX = "product";
}

持久层接口

package com.ckq.seckill.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ckq.entity.InOrder;
import org.springframework.stereotype.Repository;

@Repository
public interface InOrderDao extends BaseMapper<InOrder> {
}

package com.ckq.seckill.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ckq.entity.InOrderGoodsList;
import org.springframework.stereotype.Repository;

@Repository
public interface InOrderGoodsDao extends BaseMapper<InOrderGoodsList> {
}

package com.ckq.seckill.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ckq.entity.*;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Map;

/**
 * @ClassName InformationDao
 * @Description TODO
 * @Author XYM
 * @Date 2021/5/13 10:58
 * @Version 1.0
 **/
@Repository
public interface SeckillDao extends BaseMapper<Specification> {
    //查数据
    List<Specification> getgoodsById(Map<String,Object> map);

    //生成订单
    Integer order(Order order);
    //付款
    Integer buygodos(InOrder inOrder);

    Integer jstock(Integer id);

    Integer ujstock(Integer id);

    Integer addInOrderlist(InOrder inOrder);

    Integer addInOrderGoodsList(InOrderGoodsList inOrderGoodsList);
}

yml配置

server:
  port: 8008
spring:
  application:
    name: supply-seller-seckill
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/supply-admin?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
    username: root
    password:
    druid:
      initial-size: 1
      max-active: 10
      min-idle: 1
      max-wait: 30000
  redis:
    host: localhost
    port: 6379
    lettuce:
      pool:
        max-active: 10
        min-idle: 1
        max-idle: 10
        max-wait: 30000
    password: 123456
  sleuth:
    sampler:
      probability: 1 #取样器
  zipkin:
    sender:
      type: rabbit
#  连接rabbitMQ
  rabbitmq:
    listener:
      direct:
        retry:
          enabled: true
      simple:
        retry:
          enabled: true
          max-attempts: 3  #重试几次
          initial-interval: 5000 #次数间隔5S
        acknowledge-mode: manual
    host: localhost
    port: 5672
    username: guest
    password: guest
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
logging:
  level:
    com.ckq.product.dao: debug
#feign超时设置
feign:
  client:
    config:
      default:
        connect-timeout: 30000
        read-tmout: 30000
#表示暴露所有的节点,才能在sentienl控制台中展示实时监控信息
management:
  endpoints:
    web:
      exposure:
        include: '*'

mapping

<?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="com.ckq.seckill.dao.SeckillDao">
    <select id="getgoodsById" resultType="com.ckq.entity.Specification" parameterType="Map">
        select * from seckill_goods
        <where>
            <if test="id!=null and id!=''">and id = #{id}</if>
            <if test="goodsId!=null and goodsId!=''">and goodsId = #{goodsId}</if>
        </where>
    </select>
    <insert id="order" parameterType="com.ckq.entity.Order">
         insert into gorder (goods,price) values  (#{goods},#{price})
    </insert>
    <update id="buygodos" parameterType="map">
        update bm_inorderlist set payType=#{payType},orderStep=#{orderStep},mark=#{mark} where orderId=#{orderId}
    </update>
    <update id="jstock" parameterType="map">
        Update seckill_goods set stock=stock-1 where id=#{id} and stock>0
    </update>
    <update id="ujstock" parameterType="map">
        Update seckill_goods set stock=stock+1 where id=#{id} and stock>0
    </update>
    <insert id="addInOrderlist" parameterType="com.ckq.entity.InOrder">
        insert into bm_inorderlist (orderId,sellerId,amount,payType,orderStep,orderState,spid,deliveryAddId,createTime) values (
        #{orderId},#{sellerId},#{amount},#{payType},#{orderStep},#{orderState},#{spid},#{deliveryAddId},default
        )
    </insert>



    <insert id="addInOrderGoodsList" parameterType="com.ckq.entity.InOrderGoodsList">
        insert into bm_inordergoodslist (id,orderId,goodsId,spId,sname,goodsName,goodsImg,price,buyNum) values (
        null,#{orderId},#{goodsId},#{spId},#{sname},#{goodsName},#{goodsImg},#{price},#{buyNum}
        )
    </insert>
</mapper>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值