案例分享:使用RabbitMQ消息队列和Redis缓存优化Spring Boot秒杀功能

news2025/1/11 11:11:41

作者介绍:✌️大厂全栈码农|毕设实战开发,专注于大学生项目实战开发、讲解和毕业答疑辅导。

 推荐订阅精彩专栏 👇🏻 避免错过下次更新

Springboot项目精选实战案例

更多项目:CSDN主页YAML墨韵

学如逆水行舟,不进则退。学习如赶路,不能慢一步。

目录

1、描述

2、pom.xml文件

3、创建redis工具类

4、创建rabbitmq的配置类

5、创建数据库表

用户信息 

商品信息 

秒杀信息

订单信息

秒杀订单 

6、具体实现过程

用户登录

秒杀商品数量初始化

rabbitmq队列

生产者代码

消费者代码

订单模块

注意事项:


秒杀功能作为大型交易平台的常见活动,落地实现的时候需要应对大量并发请求,同时保证请求的快速、准确处理。本文通过案例分析,讲解如何结合Springboot 3和RabbitMQ和Redis来构建秒杀请求的异步处理队列,并通过性能测试对异步处理方案进行优化。

1、描述

交易平台秒杀功能的业务流程

秒杀活动一般发生在一些特定的时间点,如节日特卖或者是限量产品的销售。这样的活动通常会吸引大批用户的参与。由于参与的用户量大,再加上秒杀商品的数量有限,因此这种活动对后端系统的架构设计提出了很大挑战。一般来说,秒杀活动的流程可以分为以下几个步骤:

  1. 秒杀宣传与倒计时:商家通过广告和营销方式进行秒杀活动的宣传,并在秒杀页面显示一个倒计时,告知用户秒杀活动开始的时间。

  2. 用户抢单:一般来说,用户需要在秒杀页面上点击“立即秒杀”按钮,发起秒杀请求。

  3. 系统处理秒杀请求:由于秒杀活动瞬间会产生大量用户请求,所以系统要有相应的优化措施。这里我们采用的是异步处理的方式。具体来说,就是当用户发起秒杀请求后,实际上用户的请求被发送到RabbitMQ的消息队列中,然后通过后台服务进行异步处理。

  4. 检查库存:在进行后续操作之前,系统会检查当前商品的库存量,以确保没有超卖。

  5. 扣减库存并生成订单:这是一个原子操作,即系统需要在一次操作中完成库存扣减和订单生成。也就是说,当我们更新库存数量的同时,也会在订单表中创建新的订单记录。

  6. 支付处理:成功生成订单的用户被引导至支付页面进行付款操作。支付通常有一定的时间限制,如果超过时间未支付,订单会被自动取消,并将库存加回。

  7. 订单处理:用户成功支付后,系统会进行后续的订单处理工作,比如发货等。

以上是一个通用的秒杀活动业务流程。实际上,在面对大流量的情况下,需要利用各种手段进行优化,以保证服务的稳定性和用户的体验,比如引入消息队列进行异步处理,使用缓存等。具体的优化手段,还需要根据业务场景和系统状况灵活选择和实施。

2、pom.xml文件

RabbitMQ的消息队列配置请看:Spring Boot与RabbitMQ整合:实现高可用消息队列服务

redis的基本配置和实战请看:Spring Boot与Redis深度整合:实战指南

我们需要在SpringBoot应用中集成RabbitMQ和Redis。我们在pom.xml文件中添加依赖:

<dependencies>  
    <!-- Spring Boot Starter for AMQP (RabbitMQ) -->  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-amqp</artifactId>  
    </dependency>  
      
    <!-- Spring Boot Starter for Redis -->  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-data-redis</artifactId>  
    </dependency>  
      
    <!-- 其他依赖... -->  
</dependencies>

在application.yml中配置RabbitMQ和Redis:

spring:  
  rabbitmq:  
    host: your-rabbitmq-host  
    port: 5672  
    username: guest  
    password: guest  
  redis:  
    host: your-redis-host  
    port: 6379  
    password: your-redis-password # 如果设置了密码的话

3、创建redis工具类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
 
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
 
@Component
public class RedisUtil {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
 
    /**
     * 指定缓存失效时间
     * @param key  键
     * @param time 时间(秒)
     */
    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   键
     * @param delta 要增加几(大于0)
     */
    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)
     */
    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
     */
    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 对应多个键值
     */
    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)
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }
 
 
    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }
 
 
    // ============================set=============================
 
    /**
     * 根据key获取Set中的所有值
     * @param key 键
     */
    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 键
     */
    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代表所有值
     */
    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 键
     */
    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倒数第二个元素,依次类推
     */
    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 值
     */
    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  时间(秒)
     */
    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;
        }
    }
}

4、创建rabbitmq的配置类


@Configuration
public class TopicConfig {
 
    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange("seckill_topic", true, false);
    }
 
    @Bean
    public Queue seckillQueue() {
        return new Queue("seckillQueue", true);
    }
 
    @Bean
    public Binding binding() {
        return BindingBuilder.bind(seckillQueue()).to(topicExchange()).with("seckill.#");
    }
}

5、创建数据库表

用户信息 

商品信息 

秒杀信息

订单信息

秒杀订单 

6、具体实现过程

用户登录
将要秒杀的商品数量预存在redis中
接收前端传回来的用户id,被秒杀商品id
验证用户是否为当前用户,是则进行下一步
获取被秒杀的商品信息
判断用户是否成功秒杀过商品,如果秒杀过,则结束进程,否则下一步
判断被秒杀商品是否还有库存,有,下一步,否则结束进程
将用户id和被秒杀商品信息传入队列进行处理
队列处理完成后,返回结果。

    @ApiModelProperty(value = "秒杀功能")
    @PostMapping("doseckill")
    public Result doSeckill(@RequestParam Long userid, @RequestParam Long goodsid) {
     
        //判断是否当前用户
        RUser user = (RUser) redisUtil.get("user" + userid + ":");
        if (user == null) {
            return Result.fail("请登录!");
       }
 
        //缓存被秒杀商品的信息
        if (redisUtil.get("g" + goodsid + ":") == null) {
            GoodsVo goods = goodsService.selectGoodsById(goodsid);
            redisUtil.set("g" + goodsid + ":", goods);
        }
        GoodsVo goods = (GoodsVo) redisUtil.get("g" + goodsid + ":");
 
        //判断是否重复操作,已经成功秒杀,不能再次秒杀
//        SeckillOrders seckillOrders = seckillOrdersService.getOne(new QueryWrapper<SeckillOrders>()
//                .lambda().eq(SeckillOrders::getUserId, userid)
//                .eq(SeckillOrders::getGoodId, goods.getId()));
        Object o = redisUtil.get("order:" + userid + goodsid);
        if (o != null) {
            return Result.fail("用户" + userid + ":每个用户只能秒杀一次.....");
        }
        //库存预减
        Long count = redisUtil.decr("seckill" + goodsid + ":", 1);
        if (count < 0) {
            redisUtil.incr("seckill" + goodsid + ":", 1);
            return Result.fail("用户" + userid + ":存库不足,秒杀失败.....");
        }
        //将秒杀请求添加到队列中
        MqMessage message = new MqMessage(userid, goods);
        String msg = JSON.toJSONString(message);
        product.sendSeckillMessage(msg);
        return Result.succ("秒杀中............");
    }

用户登录

校验账号和密码是否正确,两者都正确则登陆成功,否则登陆失败。如果登录成功,就将用户的信息缓存在redis中,后续如果需要使用到用户的信息,直接从redis中获取即可,不用再次访问数据库。


public RUser Login(UserDto userDto) {
    RUser user = new RUser();
    BeanUtils.copyProperties(userDto,user);

    QueryWrapper<RUser> qw = new QueryWrapper<>();

    qw.lambda().eq(RUser::getId,user.getId())
        .eq(RUser::getPassword,user.getPassword());

    RUser user1 =baseMapper.selectOne(qw);
    if(user1!=null){
        redisUtil.set("user"+user1.getId()+":",user1);
        System.out.println(redisUtil.set("user"+user1.getId()+":",user1));
    }
    return user1;
}

秒杀功能的流程是:获得秒杀资格后,需要进行商品数量减少、生成订单、生成秒杀订单的操作,当三者都成功运行后,才能秒杀成功。

秒杀商品数量初始化

在系统初始化时,调用这个方法,将秒杀商品的数量存储在redis中,后续使用redis进行预减功能

    @Override
    public void getSeckillCount() {
        List<Seckill> list = baseMapper.selectList(null);
        list.forEach(System.out::println);
        if (list.size()>0) {
            list.forEach(seckill -> {
                redisUtil.set("seckill" + seckill.getGoodsId() + ":", seckill.getStockCount());
                Object o = redisUtil.get("seckill" + seckill.getGoodsId() + ":");
                System.out.println(o.toString());
            });
        }
    }

rabbitmq队列

生产者代码

把用户id和秒杀商品信息分发到队列中

public void sendSeckillMessage(String msg){
    System.out.println("发送秒杀信息"+msg);
    rabbitTemplate.convertAndSend("seckill_topic","seckill.message",msg);
}
消费者代码

从队列中获取用户id和商品信息。然后,判断该商品是否还有库存,是否已经秒杀过,如果都没有,则进行订单生产业务。

@RabbitListener(queues = "seckillQueue")
public void receive(String msg) {

    MqMessage message = JSONObject.parseObject(msg, MqMessage.class);

    Long userid = message.getUserid();

    GoodsVo goods = message.getGoodsVo();

    //判断是否还有库存
    int stock = goods.getStockCount();
    if(stock <= 0) {
        return;
    }
    //判断是否已经秒杀到了
    Orders order = ordersService.getOne(new QueryWrapper<Orders>()
        .lambda().eq(Orders::getUserId,userid)
        .eq(Orders::getGoodsId, goods.getId()));
        if(order != null) {
            return;
        }
    ordersService.seckill(userid,goods);
}
订单模块

订单模块:将用户购买的商品的数量减少1,然后生成订单和秒杀订单。三者都成功后,在redis中存储用户id和订单id,作为秒杀成功的记录,如果用户再次进行秒杀时,直接从redis查询是否存在秒杀成功的记录,存在即返回已经秒杀

public void seckill(Long userid, GoodsVo goods) {

    Seckill seckill = seckillMapper.selectOne(
        new QueryWrapper<Seckill>().lambda().eq(Seckill::getGoodsId,goods.getId()));

    seckill.setStockCount(seckill.getStockCount()-1);
    seckillMapper.updateById(seckill);

    //生成订单
    Orders orders = new Orders();
    orders.setUserId(userid).setGoodsId(goods.getId())
        .setGoodsCount(1).setStatu(0).setCreateDate(new Date())
        .setGoodsPrice(seckill.getSeckillPrice());
    baseMapper.insert(orders);

    //生成秒杀订单
    SeckillOrders seckillOrders = new SeckillOrders();
    seckillOrders.setOrderId(orders.getId());
    seckillOrders.setUserId(userid);
    seckillOrders.setGoodId(goods.getId());

    // 保存秒杀订单信息
    int i=seckillOrdersMapper.insert(seckillOrders);

    redisUtil.set("order:"+userid+goods.getId(),i);
 
}

注意事项:

  1. 性能优化:在实际生产环境中,您可能需要考虑使用Redis的Lua脚本来确保库存扣减的原子性,并考虑使用分布式锁来避免超卖。
  2. 限流:在秒杀场景下,大量的请求可能会导致系统崩溃。因此,您可能需要在应用层或网络层实现限流策略。
  3. 异步处理:使用RabbitMQ进行异步处理可以确保秒杀接口的快速响应,并避免同步处理订单逻辑导致的性能瓶颈。
  4. 持久化:虽然上述案例中没有明确提到,但您应该确保将秒杀成功的订单信息持久化到数据库中,以便后续的处理和查询。
  5. 错误处理:在分布式系统中,错误处理是非常重要的。您应该确保在RabbitMQ消费者中正确处理各种异常情况,并考虑使用重试机制来确保消息的可靠传递。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1635920.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

设计模式(十一):外观模式

设计模式&#xff08;十一&#xff09;&#xff1a;外观模式 1. 外观模式的介绍2. 外观模式的类图3. 外观模式的实现3.1 创建一个接口3.2 创建接口的实现3.3 创建一个外观类3.4 测试 1. 外观模式的介绍 外观模式&#xff08;Facade Pattern&#xff09;属于结构型模式&#xf…

智能科技的飞跃:LLAMA3引领的人工智能新时代

大家好&#xff01;相信大家对于AI&#xff08;人工智能&#xff09;的发展已经有了一定的了解&#xff0c;但你是否意识到&#xff0c;到了2024年&#xff0c;AI已经变得如此强大和普及&#xff0c;带来了我们从未想象过的便利和创新呢&#xff1f;让我们一起来看看AI在这个时…

【javaWeb项目】基于网页形式,通过浏览器访问的java应用程序,就称为javaweb程序

JavaWeb前端 第一章 1、javaWeb是什么 //基于网页形式&#xff0c;通过浏览器访问的java应用程序&#xff0c;就称为javaweb程序2、web程序的分类 //1、静态web程序特点&#xff1a;网页上的内容是固定不变的&#xff0c;不能动态加载&#xff0c;例如web前端//2、动态web程序…

chrome和drive安装包路径

Chrome for Testing availability (googlechromelabs.github.io) 下载Stable下面的包哈

深入剖析Tomcat(五) 剖析Servlet容器并实现一个简易Context与Wrapper容器

上一章介绍了Tomcat的默认连接器&#xff0c;后续程序都会使用默认连接器。前面有讲过Catalina容器的两大块内容就是连接器与Servlet容器。不同于第二章的自定义丐版Servlet容器&#xff0c;这一章就来探讨下Catalina中的真正的Servlet容器究竟长啥样。 四种容器 在Catalina中…

VTK —— 二、教程六 - 为模型加入3D微件(按下i键隐藏或显示)(附完整源码)

代码效果 本代码编译运行均在如下链接文章生成的库执行成功&#xff0c;若无VTK库则请先参考如下链接编译vtk源码&#xff1a; VTK —— 一、Windows10下编译VTK源码&#xff0c;并用Vs2017代码测试&#xff08;附编译流程、附编译好的库、vtk测试源码&#xff09; 教程描述 本…

数据挖掘之基于Lightgbm等多模型消融实验的信用欺诈检测实现

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 在当前的金融环境中&#xff0c;信用欺诈行为日益增多&#xff0c;给金融机构和消费者带来了巨大的损…

RS0108YQ20功能和参数介绍及高速数据传输中的优势

RS0108YQ20功能和参数介绍及高速数据传输中的优势-公司新闻-配芯易-深圳市亚泰盈科电子有限公司 RS0108YQ20是一款电平转换器&#xff0c;也称为电平移位器&#xff0c;它的主要功能是在不同的电源电压或逻辑电平之间提供双向信号转换。以下是RS0108YQ20的一些关键参数和功能特…

使用UmcFramework和unimrcpclient.xml连接多个SIP设置的配置指南及C代码示例

使用UmcFramework和unimrcpclient.xml连接多个SIP设置的配置指南及C代码示例 引言1. UniMRCP和UmcFramework简介2. 准备工作3. unimrcpclient.xml配置文件3.1 定义SIP设置3.2 定义MRCP会话配置文件 4. C代码示例5. 测试和验证6. 故障排查7. 结论8. 参考文献 引言 在多媒体通信…

Django后台项目开发实战二

我们的需求是开发职位管理系统 三个功能&#xff1a; 管理员发布职位候选人能浏览职位用户能投递职位 第二阶段 创建应用 jobs&#xff0c;实现职位数据的建模 python manage.py startapp jobs 然后再 setting .py 注册应用&#xff0c;只需添加应用名称到最后一行 INST…

单片机排队叫号系统Proteus仿真程序 有取号键和叫号键以及重复叫号键 有注释

目录 1、前言 ​ 2、程序 资料下载地址&#xff1a;单片机排队叫号系统Proteus仿真程序 有取号键和叫号键以及重复叫号键 有注释 1、前言 系统组成&#xff1a;STC89C52RCLcd1602蜂鸣器按键 具体介绍&#xff1a; Lcd1602排队叫号系统&#xff0c;有取号显示窗和叫号显示窗…

Ubuntu 16.04下Firefox版本更新

最近要使用Odoo进行项目管理&#xff0c;Odoo17以上版本对浏览器版本要求较高&#xff0c;如果没有新版本下的函数&#xff0c;将无法运行。而Ubuntu16.04下自带的firefox不满足版本要求&#xff0c;因而需要手动下载安装。 查看当前系统版本apt-get能下载的firefox版本 apt-c…

排序算法大总结

引言 排序算法&#xff08;sorting algorithm&#xff09;是用于对一组数据按照特定顺序进行排列。排序算法有着广泛的应用&#xff0c;因为有序数据通常能够被更高效地查找、分析和处理。 如图 1-1 所示&#xff0c;排序算法中的数据类型可以是整数、浮点数、字符或字符串等…

ubuntu下anaconda虚拟环境开机自启动

&#xff08;1&#xff09; 要在Ubuntu系统中使Anaconda环境下的Python脚本在开机时自启动&#xff0c;可以通过创建一个systemd服务单元来实现。以下是步骤和示例代码&#xff1a; 创建一个新的systemd服务文件。 打开文本编辑器&#xff0c;创建一个新的服务文件。例如&…

idea生成双击可执行jar包

我这里是一个生成xmind,解析sql的一个main方法,可以通过配置文件来修改有哪些类会执行 我们经常会写一个处理文件的main方法,使用时再去寻找,入入会比较麻烦,这里就可以把我们写过的main方法打成jar包,放到指定的目录来处理文件并生成想要的结果 1.写出我们自己的main方法,本地…

【Java笔记】JVM:对象在内存中是什么样的?如何计算对象占用的内存大小?

文章目录 Java对象的内存布局计算对象占用的内存大小Openjdk jol来算几个Object o new Object() 该对象在内存中占用多少字节&#xff1f;基本数据类型作为成员变量的对象有实例对象作为成员变量的对象 Java对象的内存布局 Java中&#xff0c;一个实例对象在内存中的组成主要包…

(40)4.30数据结构(队列)

1.队列的基本概念 2.队列的顺序 #define MaxSize 10 #define ElemType int typedef struct { ElemType data[MaxSize]; int front, rear; }SqQueue;//1.初始化操作 void InitQueue(SqQueue& Q) { //初始化 队头&#xff0c;队尾指针指向0 Q.rear Q.fron…

大数据分析与内存计算学习笔记

一、Scala编程初级实践 1.计算级数&#xff1a; 请用脚本的方式编程计算并输出下列级数的前n项之和Sn&#xff0c;直到Sn刚好大于或等于q为止&#xff0c;其中q为大于0的整数&#xff0c;其值通过键盘输入。&#xff08;不使用脚本执行方式可写Java代码转换成Scala代码执行&a…

Apache中如何配置 ws 接口

Apache中如何配置 wss 接口 在Apache中配置WebSockets的支持&#xff0c;你需要使用mod_proxy_wstunnel模块&#xff0c;该模块是Apache的一个代理模块&#xff0c;它允许你代理WebSocket请求。 以下是配置步骤的简要说明和示例&#xff1a; 确保你的Apache服务器安装了mod_…

由于找不到msvcr80.dll,无法继续执行代码的解决方法

在日常使用电脑进行工作或娱乐时&#xff0c;您可能会遇到一个令人困惑的情况&#xff1a;屏幕上突然弹出一个错误提示&#xff0c;明确指出“msvcr80.dll文件丢失”&#xff0c;这个错误通常会导致某些应用程序无法正常运行。那么&#xff0c;当我们遇到这个问题时&#xff0c…